This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-05-23
Channels
- # ai (1)
- # aleph (1)
- # announcements (7)
- # babashka (87)
- # beginners (34)
- # biff (9)
- # clerk (4)
- # clojars (37)
- # clojure (144)
- # clojure-art (12)
- # clojure-europe (13)
- # clojure-nl (1)
- # clojure-norway (4)
- # clojure-uk (2)
- # clr (5)
- # conjure (1)
- # data-science (1)
- # datahike (7)
- # datalevin (6)
- # datomic (13)
- # events (1)
- # fulcro (1)
- # graalvm (5)
- # gratitude (1)
- # honeysql (4)
- # hyperfiddle (122)
- # malli (26)
- # nbb (2)
- # off-topic (16)
- # portal (93)
- # practicalli (1)
- # re-frame (1)
- # reitit (15)
- # releases (3)
- # remote-jobs (1)
- # shadow-cljs (5)
- # tools-deps (6)
- # xtdb (4)
Is there a nice way to reuse an e/fn
across other e/fn
s?
;; save-label is an e/fn that i want to reuse with multiple dom/on event triggers
(let [save-label
(e/fn [e]
(if-let [label (not-empty (.. e -target -textContent))]
(e/server (do-stuff! label))
(println "empty label")))]
(dom/on "blur" (e/fn [e] (save-label e)))
(dom/on "keydown"
(e/fn [e]
(when (= "Enter" (.-key e))
(.preventDefault e)
(save-label e)
(.. e -target blur)))))
I think this will work for you:
(let [save-label
(e/fn [e]
(if-let [label (not-empty (.. e -target -textContent))]
(e/server (do-stuff! label))
(println "empty label")))]
(dom/on "blur" (e/fn [e] (save-label e)))
(dom/on "keydown"
(e/fn [e]
(when (= "Enter" (.-key e))
(.preventDefault e)
(new save-label e)
(.. e -target blur)))))
The calling convention for electric functions is to use new
I guess it’s more than a convention, it’s the law 🙂
question that's been on the back of my mind, does new
correspond directly to some missionary concept? Does e/fn
create a task that new
essentially calls m/?
on (I think that can't be exactly it but maybe is it something like that)? I'm overdue to do a missionary refresher and I've been curious how exactly it relates to electric. I know it's implemented with it and deeply integrated, but I'm not sure where it is directly used vs wrapped.
As I recall a comment that @U09K620SG made, it has to do with a limitation of clojure to supply the necessary symbol / fn metadata so that the electric compiler can properly identify electric functions in all cases (related to anonymous functions, maybe?)
Regardless, you’re not new
ing up something in the javascript/java sense. You’re simply letting the electric compiler know that you’re referencing an electric function rather than a regular clojure fn
new
(thinking from the missionary layer) corresponds to monadic join on missionary flows, which looks something like (m/cp (m/?< (m/?< <<x))
- given a flow-of-flows, it returns a flow, removing one layer. This is the same concept as async/await on promises
the fact that new
also boots e/fn
is revealing compiler internals, e/fn
compiles down literally to a missionary flow. the e/fn parameters are compiled down to flows in dynamic scope that the e/fn flow will access
TLDR
• new
is await
for flows - (let [x (new (m/observe ...)) ...)
• new
boots Electric e/fns - (let [x (new (e/fn [] ...)) ...)
• it turns out these two operators are the same thing due to compiler internals
ah, we have old docs about this - https://github.com/hyperfiddle/electric/blob/master/src-docs/user/electric/electric_missionary_interop.clj
When deploying an Electric app to Fly via GH actions, the build alias deps don’t seem to be cached.
#12 [clojure-deps 6/6] RUN clojure -T:build noop # preload build deps
#12 CACHED
# further down:
#19 [build 9/9] RUN clojure -X:build uberjar :jar-name "app.jar" :verbose true :version '"'9f539d3'"'
#19 1.611 Cloning:
#19 2.874 Cloning:
#19 4.432 Checking out: at 9bd8b8a3c459966954b21c136e7b1084cb5e0fb0
#19 4.458 Checking out: at ba1a2bf421838802e7bdefc541b41f57582e53b6
#19 5.063 Cloning:
#19 6.216 Checking out: at e3e353262072e95ccac314a9b935b1bc42412a40
#19 6.239 Checking out: at 55fb6f63ea3cc5344e67e87d2322570d4dddd3d5
#19 6.669 Downloading: commons-io/commons-io/2.10.0/commons-io-2.10.0.pom from central
#19 6.814 Downloading: org/codehaus/plexus/plexus-interpolation/1.25/plexus-interpolation-1.25.pom from central
#19 6.818 Downloading: org/apache/maven/maven-builder-support/3.8.2/maven-builder-support-3.8.2.pom from central
Is this the right command to trigger fetching :build alias deps?
RUN clojure -T:build noop
It is the right alias flag.This is the right alias flag. The issue is git deps are prefetched but aren’t carried over to the next build step. So they are fetched a second time. Unlike maven deps (Dockerfile has a COPY
instruction for .m2
).
Update: resolved by calling (time-literals.read-write/print-time-literals-clj!)
in prod.clj. There is probably a #time/date in a namespace that entrypoint depends on.
I’m heavily using https://github.com/henryw374/time-literals/ in an Electric project. Runs fine locally in dev, but when I deploy to http://Fly.io, it throws a java.lang.ExceptionInInitializerError
on startup despite calling (time-literals.read-write/print-time-literals-clj!)
at top of my app namespace:
2023-05-23T15:03:40.205 app[1781756a99ed89] jnb [info] at clojure.lang.Util.loadWithClass(Util.java:251)
2023-05-23T15:03:40.205 app[1781756a99ed89] jnb [info] at prod.<clinit>(Unknown Source)
2023-05-23T15:03:40.205 app[1781756a99ed89] jnb [info] Caused by: java.lang.IllegalStateException: Attempting to call unbound fn: #'time-literals.data-readers/date
2023-05-23T15:03:40.205 app[1781756a99ed89] jnb [info] at clojure.lang.Var$Unbound.throwArity(Var.java:45)
2023-05-23T15:03:40.205 app[1781756a99ed89] jnb [info] at clojure.lang.AFn.invoke(AFn.java:32)
Is the namespace containing (time-literals.read-write/print-time-literals-clj!)
at the top actually loaded/required in your prod setup?
Thanks @U2DART3HA, I just moved that call to prod.clj (+ require) and now it seems to pass health check on Fly.
Now getting 502 due to Connection state changed (MAX_CONCURRENT_STREAMS == 32)!
on Fly, which is not Electric-related (I don’t think).
How much memory does my Electric XTDB app need on http://Fly.io? My deployed app ran out of memory (256MB).
I don’t know how much your app is going to use. 256MB is low for a JVM app anyway. Bumping it to 1 or 2G is not expensive.
OK so 512MB seems to have worked, but now I’m getting Missing client program manifest
. Must be missing a file…
rn Electric only works with in-process XT. Why is that? Can it poll XT HTTP server so I can put XT on a bigger machine?
(and also to share state)
That's a question for the XTDB folks
Electric can integrate any event stream that XTDB provides
Has anyone tried to poll XT’s HTTP tx-log and turn it into a missionary stream?
Deploying to http://Fly.io via GH actions frequently blocks because Waiting for remote builder fly-builder-wispy-wildflower-9951...
When I run fly deploy
on my machine, it seems to release the builder and unblock the GH build job, at which point I can Ctrl+C my local fly deploy.
We have seen similar intermittent issues with fly. It’s not related to electric. Maybe have a look at the --remote-only
and --local-only
fly deploy
flags?
Deleted my Fly builder machine and retriggered GH deploy job to see if that fixes it.
To echo what Geoffrey said - fly goes down a lot
What does Missing client program manifest
mean in prod?
It means the client-side code might not have been compiled, or failed to compile. Look at your fly deploy
output for cljs compilation errors. Prefix your fly deploy
command with NO_COLOR=1
to get the full logs : NO_COLOR=1 flyctl deploy …
When building the client-side code, the cljs compiler generates a manifest.edn
file, sitting next to the compiled .js
files. In prod, js
artifacts are fingerprinted (`app.<SHA256>.js`) for proper cache invalidation.
The server needs to read the manifest file to serve the js file.
Ah, thanks. I was missing :prod build in shadow-cljs.edn and didn’t see it because previous failure was cached. Surprised it did not error out, just showed:
#19 24.30 Building client. Version: e7d2c50
#19 33.68 shadow-cljs - server version: 2.20.1 running at
#19 33.68 shadow-cljs - nREPL server started on port 9001
#19 33.68 No configuration for build ":prod" found.
#19 34.69 Bundling sources
#19 34.73 Compiling server. Version: e7d2c50
Hello, I'm new to Electric and want to give it a try. The situation I have is the following:
• The back-end is already built and has an endpoint Get /events
that returns streams SSE
• I want to make a GET request to /events
and constantly react and render something based on what the end-point is returning.
The demos I saw render stuff based on the db changes and from what I understand websockets are happening under-the hood. I don't know if electric would be suitable for my case, any guidance on how to start are very helpful.
Here is the draft code snippet that serves the streams https://github.com/bob-cd/bob/issues/119#issuecomment-1546904931
I've been doing something like this with my first electric project and it definitely works. I don't quite have a super great understanding how it works and have been curious about some things related to it. I've been using the OpenAI api to get streaming responses. So on my server I make an api request to them, and get a SSE response, which I turn into a lazy sequence from of the streaming results. I keep the state like this:
#?(:clj (defonce !msg-id->frags (atom {})))
(e/def msg-id->frags (e/server (e/watch !msg-id->frags)))
Each streamed fragment I get is conj'd into the atom like:
(swap! !msg-id->frags update resp-msg-id (fnil conj []) frag)
Then in any electric code I can just refer to msg-id->frags
it it will reflect the latest changes.
I have been curious if, under the hood, this entire map is sent across the wire every time a change is made, or if just a diff of the old and new map is sent.
Either way, I could imagine for various reasons wanting to just stream something directly from the server to the client. From browsing the source I'm pretty sure this is possible but too advanced for me at the moment, was hoping to learn more of electric via osmosis before attempting it. I think this stuff might be relevant: https://github.com/hyperfiddle/electric/blob/a65ed416fb0ec8320b43b135bf4c5c1df0a6a607/src/hyperfiddle/electric.cljc#L435-L490
For my use case just watching the atom works well for now.Also, Dustin's answers to my question from a few days ago might be relevant: https://clojurians.slack.com/archives/C7Q9GSHFV/p1684442698215489?thread_ts=1684442493.333779&cid=C7Q9GSHFV
Thank you very much, this indeed works as a starting point. I'll give it a try, it is good enough for the moment if all the seq is passed, later on we can see how to optimize it if need it. I'll spend some time playing around with the content yous shared. The example you mentioned is public code that you could share?
I'm working on polishing it off to open source as we speak, possibly today or tomorrow
there is no diffing on msg-id->frags
, the only Electric primitive that performs diffing is e/for-by
Don't use e/for-event
from master (don't use master at all actually), this is WIP. That is a low level API that is hard to use and we are trying to rework before releasing it.
As to how to integrate OpenAI streaming responses, there is certainly a better way to do it than accumulating an atom and watching that, can you share some example responses and how you are trying to render them?
But to be clear, the atom is fine if it's working for you
What if we use datascript to persist everytime a event is received? and query for the events to render them in the client? similar to the https://electric.hyperfiddle.net/user.demo-todos-simple!TodoList it uses the e/for-by
that strategy consumes unbounded memory (same as using (atom []) as a queue)
If it works for you, it's fine
how would you approach the problem? different solutions work fine and probably for the scale of this problem it's okay.
I need to see some example responses and how you are trying to render them then i'll tell you 🙂
fair enough, I'll code something and come back 🙂
> I'm working on polishing [my openai electric demo app] off to open source as we speak, possibly today or tomorrow > As to how to integrate OpenAI streaming responses, there is certainly a better way to do it than accumulating an atom and watching that, can you share some example responses and how you are trying to render them? Got my electric demo out here: https://github.com/jjttjj/chatCLJ Definitely still needs some rounds of refactoring which I'll keep doing over time. In particular this streaming response stuff. Basically on the server I have a function that makes the streaming api request and returns an eduction of message fragments
(into []
(comp (map #(select-keys % [:choices])) ;; I only use this key
(take 4))
(cljgpt.openai/streaming-request example-request))
[{:choices
[{:delta {:role "assistant"}, :index 0, :finish_reason nil}]}
{:choices [{:delta {:content "Why"}, :index 0, :finish_reason nil}]}
{:choices [{:delta {:content " did"}, :index 0, :finish_reason nil}]}
{:choices [{:delta {:content " the"}, :index 0, :finish_reason nil}]}]
I just conj each of these fragments into a map of msg-id->frag, in an atom that is `e/watched, (while also building up a completed message that will eventually be saved)
https://github.com/jjttjj/chatCLJ/blob/78629d13f97522d3e47f590573b9ff51ada5e778/src/chatclj/app.cljc#L181C8-L191
Then I want to render these fragments so when listing all messages for a chat, if one doesn't yet have a content key I lookup the fragments in msg-id->frags
and do a (for-by identity ...)
to show each fragment
https://github.com/jjttjj/chatCLJ/blob/78629d13f97522d3e47f590573b9ff51ada5e778/src/chatclj/app.cljc#L267C21-L270So at a super high level in bad "pseudocode", what I'm doing looks like this:
(def db (atom {1 "msg1" 2 "msg2" 3 :placeholder})) ;;<- this is the db in the real app
(let [;;frags isn't in the db, it's only temporary and could have it's scope limited to just the code below
frags (atom {3 "m"}) ;; eventually "msg3"
user-question "what's 1+1"]
(for [[id msg] db]
(if (= msg :placeholder)
(show-fragments id)
(show-msg msg)))
(input
(on :enter-keypress
(fn [e]
(let [msg-id 3
answer-reducible (streaming-request user-question)
;;ie ["a" "b" "c" ...]
]
;; the most straightforward thing
(run! #(swap! frags update msg-id str %) answer-reducible))))))
I guess te question is, is there a way in electric to directly stream something on the server to something atom-like that can be watched on the client?Actually, sorry. I realized this was already answered in my earlier slack question that I even linked to above, and I just need to take a serious stab at those methods (I got this working and moved on to other stuff before trying them deeply). But I guess I'll leave this here because it was sort of useful to re-state the situation
I tried the chatclj app on my machine, I get 404 errors in the log when I submit a prompt. The api key is set as you see in bearers
. Have I done something wrong?
clojure.lang.ExceptionInfo: Exceptional status code: 404
body: #object[java.util.zip.GZIPInputStream 0x410d7a00 "java.util.zip.GZIPInputStream@410d7a00"]
headers: {"content-encoding" "gzip",
"server" "cloudflare",
"content-type" "application/json; charset=utf-8",
"alt-svc" "h3=\":443\"; ma=86400",
"strict-transport-security" "max-age=15724800; includeSubDomains",
"cf-cache-status" "DYNAMIC",
"cf-ray" "7cdfc40dd9ca8ccc-EWR",
":status" "404",
"date" "Sat, 27 May 2023 16:52:00 GMT",
"vary" "Origin",
...}
request: {:headers
{:accept "*/*",
:accept-encoding ["gzip" "deflate"],
:user-agent "babashka.http-client/0.1.4",
"Content-Type" "application/json",
"Authorization"
"Bearer sk-qfi...H1tX"}, // Open API key
:body
"{\"model\":\"gpt-4\",\"messages\":[{\"role\":\"system\",\"content\":\"You are a helpful assistant.\"},{\"role\":\"user\",\"content\":\"what is \\\"ring\\\" in category theory?\"},{\"role\":\"user\",\"content\":\"translate \\\"hello\\\" to french\"}],\"stream\":true}",
:as :stream,
:uri "",
:method :post}
status: 404
version: :http2
Ok, I read the thread and looked at the code in the links. The stated question: "is there a way in electric to directly stream something on the server to something atom-like that can be watched on the client?" I don't understand the question and I also don't understand why you would ask that, I think there is missing context. Can we start with the problem – what problem are you facing that makes you ask this question? Please answer in the language of the end user, please do not answer in the language of programming
> I tried the chatclj app on my machine, I get 404 errors in the log when I submit a prompt.
Hmm, just to rule some things out, you have a funded openAI account right? It looks like that request you made is using gpt-4 which I think still requires getting on a waitlist and isn't generally available. I should put this in my readme. When you start a chat with gpt 3.5 do you still get the 404? You should be able to try out api requests here to confirm that they work for your openai account: https://platform.openai.com/playground?mode=chat
> The stated question: "is there a way in electric to directly stream something on the server to something atom-like that can be watched on the client?"
> Can we start with the problem – what problem are you facing that makes you ask this question?
Good point, it's definitely not a user-facing problem. Partly just asking scattershot questions to get up to speed on Electric, and curious if your earlier point of:
> As to how to integrate OpenAI streaming responses, there is certainly a better way to do it than accumulating an atom and watching that, can you share some example responses and how you are trying to render them?
still applied.
I was also a bit "worried" that the entire accumulated sequence of fragments was being sent over the wire each time an new fragment was received on the server, but I think that is avoided due to using e/for-by
to iterate over the fragments here: https://github.com/jjttjj/chatCLJ/blob/c875af3fa030459edc60ac8c35681b37eb32897c/src/chatclj/app.cljc#L313-L316
This would be a premature-optimization at this point in any case, what I have works perfectly here. I have done things in the past that required direct server to client ui streaming of large amounts of data, so just a little curious.
I do think I'm at a point now where I have enough context with Electric and a backlog of examples to try out that I've found in the repo that I could make an attempt at this when the need arises and have more specific questions then 🙂
yes I pay for ChatGPT and have GPT4 access
Ah, I guess paying for the OpenAI API is different than paying for ChatGPT ?
I now see that it is, and my "free usage has expired" thanks
you'll want to wrap that e/for-by in an e/server for (get msg-id->frags id)
to run on the server and therefore individual frag
to stream; currently the e/for-by is on the client so (get msg-id->frags id)
is running on the client, which means (e/def msg-id->frags (e/server (e/watch !msg-id->frags)))
the entire atom value is streaming to the client so the get
can run on the client
The point is that Electric does give you what you need to reason directly about efficient network transfers
If your question is: "How do I stream a sequence of server events/delta/frags to the client efficiently": Two things to consider: • minimizing network traffic • cost of diffing
Minimizing network traffic can be done with e/for-by
as I described a moment ago
Cost of diffing is: if OpenAI is giving us a stream of events/deltas/frags, why would we collect them into an atom with (swap! !msg-id->frags update resp-msg-id (fnil conj []) frag)
(specifically conj
) only to pass the collection to e/for-by which is going to separate them back into deltas by computing diffs?
Also, storing the whole collection in an atom on the server costs unbounded memory, and if that collection gets huge, and you have to diff it back into frags each time it changes, ...
Ah, the e/server
wrapping now makes perfect sense. This worked on the first go and reads very nicely in that it's clear that it only streams the changes
The solution here (today) is tricky, it would involve dropping down to missionary and bypassing e/for-by entirely. We're working on an alternate primitive to e/for-by, perhaps called e/for-integrate-by
or something which accepts a discrete event stream as an input (thereby skipping the diffing step)
So for now I would continue doing what you're doing, as obviously it works well enough despite the theoretical flaws
Maybe this is shadow-cljs specific, but GH deployment to Fly does not stop if Cljs build fails due to a missing package.json dependency. This causes CI to deploy a broken image that passes health check.
Where should I add a call to yarn
or npm install
in the client build pipeline? Seems like my cljs dependencies in package.json are not being installed.
According to Shadow-cljs, docs supposed to call it: https://shadow-cljs.github.io/docs/UsersGuide.html#npm-install
I see there is a node-deps step on example app now: https://github.com/hyperfiddle/electric-examples-app/blob/60e2e6f8c34c6397da91af7d067bd977a75240c1/Dockerfile#L1-L4 What’s the latest? Is there a reference Dockerfile for Electric XT?
Also look at line 8.
Your link points to the latest.
This is what I have:
FROM node:14.7-stretch AS node-deps
WORKDIR /app
COPY package.json package.json
RUN npm install
FROM clojure:openjdk-11-tools-deps AS clojure-deps
WORKDIR /app
COPY deps.edn deps.edn
COPY src-build src-build
RUN clojure -A:dev -M -e :ok # preload deps
RUN clojure -T:build noop # preload build deps
FROM clojure:openjdk-11-tools-deps AS build
WORKDIR /app
COPY --from=clojure-deps /root/.m2 /root/.m2
COPY --from=node-deps /app/node_modules /app/node_modules
COPY shadow-cljs.edn shadow-cljs.edn
COPY deps.edn deps.edn
COPY src src
COPY src-build src-build
COPY resources resources
ARG REBUILD=unknown
ARG VERSION
RUN clojure -X:build uberjar :jar-name "app.jar" :verbose true :version '"'$VERSION'"'
FROM amazoncorretto:11 AS app
WORKDIR /app
COPY --from=build /app/app.jar app.jar
EXPOSE 8080
ARG VERSION
ENV VERSION=$VERSION
CMD java -DHYPERFIDDLE_ELECTRIC_SERVER_VERSION=$VERSION -jar app.jar
Can I replace it with the reference? (for Electric XT)
Note this is for XT, not Datomic.You cannot just replace it, lines 10 to 28 are not relevant outside of the electric-examples-app
repo.
> [build 7/13] COPY .m2 /root/.m2:
------
Error: failed to fetch an image or build from source: error building: failed to compute cache key: "/.m2" not found: not found
^ alsocurrently blocked on this. should it be COPY /root/.m2 /root/.m2
?
There is a .m2
in electric-examples-app
, but probably not in your project.
should I create it? isn’t that supposed to be my machine’s m2 cache?
Unless you need to set maven repository credentials, then you don’t need it and can drop the line in the Dockerfile.
Isn’t .m2 related to caching maven deps? I’m trying with this step added back:
FROM clojure:openjdk-11-tools-deps AS clojure-deps
WORKDIR /app
COPY deps.edn deps.edn
COPY src-build src-build
RUN clojure -A:dev -M -e :ok # preload deps
RUN clojure -T:build noop # preload build deps
FROM clojure:openjdk-11-tools-deps AS build
WORKDIR /app
COPY --from=clojure-deps /root/.m2 /root/.m2
...
fuuu image size is now 766MB, but it fixes my missing node deps.
now stuck on required namespace "user" is not available
(src/user.cljs is committed):
#22 19.42 [:prod] Compiling ...
#22 19.95 -> build target: :browser stage: :configure
#22 19.96 <- build target: :browser stage: :configure (3 ms)
#22 19.96 -> Resolving Module: :main
#22 19.96 The required namespace "user" is not available.
#22 19.96
#22 DONE 20.5s
#23 exporting to image
Build still continues and deploys with missing manifest error.Maybe a classpath issue. Maybe some of your prod code references user/something
.
hmm nope. Here is src/user.cljs
:
(ns ^:dev/always user ; Electric currently needs to rebuild everything when any file changes. Will fix
(:require
app.electric ;; my app entrypoint
hyperfiddle.electric
hyperfiddle.electric-dom2))
(prn "loading user namespace")
(def electric-main
(hyperfiddle.electric/boot ; Electric macroexpansion - Clojure to signals compiler
(binding [hyperfiddle.electric-dom2/node js/document.body]
(app.electric/MyApp.))))
(defonce reactor nil)
(defn ^:dev/after-load ^:export start! []
(prn "start")
(assert (nil? reactor) "reactor already running")
(set! reactor (electric-main
#(js/console.log "Reactor success:" %)
#(js/console.error "Reactor failure:" %))))
(defn ^:dev/before-load stop! []
(prn "stop")
(when reactor (reactor)) ; teardown
(set! reactor nil))
Oh you said prod code…Hmm here is src/prod.clj:
(ns prod
(:gen-class)
(:require
time-literals.read-write
app.electric
clojure.string
;electric-server-java11-jetty10
electric-server-java8-jetty9))
(time-literals.read-write/print-time-literals-clj!) ;; here because complains on startup if in Electric.
(def electric-server-config
{:host "0.0.0.0", :port 8080, :resources-path "public"})
(defn -main [& args] ; run with `clj -M -m prod`
(when (clojure.string/blank? (System/getProperty "HYPERFIDDLE_ELECTRIC_SERVER_VERSION"))
(throw (ex-info "HYPERFIDDLE_ELECTRIC_SERVER_VERSION jvm property must be set in prod" {})))
(electric-server-java8-jetty9/start-server! electric-server-config))
; On CLJS side we reuse src/user.cljs for prod entrypoint
the only thing that changed recently is time-literals.> the only thing that changed recently is time-literals. are you saying this used to work and then stopped working at a specific commit?
Would it be possible to run the Electric cljs build and the clj uberjar build in parallel?
Why would prod shadow-cljs build complain that The required namespace "user" is not available
when src/user.cljs is committed?
My shadow-cljs.edn matches https://github.com/hyperfiddle/electric-examples-app/blob/60e2e6f8c34c6397da91af7d067bd977a75240c1/shadow-cljs.edn.
When I run clojure -X:build build-client
locally, prod build seems to hang:
Building client. Version: 0e62a92-dirty
shadow-cljs - server version: 2.20.1 running at
shadow-cljs - nREPL server started on port 9001
[:prod] Compiling ...
TRACE hyperfiddle.electric.impl.env: initial load opentax.electric
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external java.math.RoundingMode
TRACE hyperfiddle.electric.impl.env: loading external java.math.RoundingMode
TRACE hyperfiddle.electric.impl.env: loading external java.math.RoundingMode
# it just waits here forever.
Gonna try with verbose & debug.So everything runs fine locally, but on GH actions it fails:
#22 [build 10/10] RUN clojure -T:build build-client :verbose true :version '"'fdf113d'"'
#22 1.236 Cloning:
#22 1.667 Cloning:
#22 2.061 Checking out: at ba1a2bf421838802e7bdefc541b41f57582e53b6
#22 2.081 Checking out: at 9bd8b8a3c459966954b21c136e7b1084cb5e0fb0
#22 2.248 Cloning:
#22 2.565 Checking out: at e3e353262072e95ccac314a9b935b1bc42412a40
#22 2.582 Checking out: at 55fb6f63ea3cc5344e67e87d2322570d4dddd3d5
#22 12.79 fatal: not a git repository (or any of the parent directories): .git
#22 14.02 Building client. Version: fdf113d
#22 19.44 shadow-cljs - server version: 2.20.1 running at
#22 19.44 shadow-cljs - nREPL server started on port 9001
#22 19.45 [:prod] Compiling ...
#22 19.97 -> build target: :browser stage: :configure
#22 19.97 <- build target: :browser stage: :configure (4 ms)
#22 19.97 -> Resolving Module: :main
#22 19.98 The required namespace "user" is not available.
#22 19.98
#22 DONE 20.5s
Resolved by using the Dockerfile from electric-starter-app, not the commands from electric-examples-app.
So instead of this from electric-examples-app:
RUN clojure -T:build build-client :verbose true :version '"'$VERSION'"'
Use:
RUN clojure -X:build uberjar :jar-name "app.jar" :verbose true :version '"'$VERSION'"'
How long does advanced cljs build typically take? dev build takes ~30s on my machine, but prod seems to take forever (or hangs).
Trying now with clojure -X:build build-client verbose=true
:
[:prod] Compiling ...
-> build target: :browser stage: :configure
<- build target: :browser stage: :configure (3 ms)
-> Resolving Module: :main
<- Resolving Module: :main (1145 ms)
...
<- Cache write: com/rpl/specter.cljc (129 ms)
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external clojure.lang.PersistentArrayMap
TRACE hyperfiddle.electric.impl.env: loading external java.math.RoundingMode
<- Compile CLJS: user.cljs (17209 ms)
-> build target: :browser stage: :compile-finish
<- build target: :browser stage: :compile-finish (1 ms)
-> build target: :browser stage: :optimize-prepare
<- build target: :browser stage: :optimize-prepare (0 ms)
-> Closure - Optimizing ...
Optimizing CLJS Constants took 1071ms
... ;; hangs here.
…
<- Closure - Optimizing ... (186481 ms)
-> build target: :browser stage: :optimize-finish
<- build target: :browser stage: :optimize-finish (0 ms)
-> build target: :browser stage: :flush
-> Flushing optimized modules
Flushing: main.D52B65600F20EE2C84CB5486269BBD07.js (22972665 bytes)
<- Flushing optimized modules (1723 ms)
<- build target: :browser stage: :flush (2384 ms)
[:prod] Build completed. (223 files, 91 compiled, 0 warnings, 232.74s)
phew OK, advanced build takes about 4 minutes (with debug setting).any tips to speed this up? maybe ditch Specter in client?
Aside, I get these Missionary warnings on advanced build:
Optimizing CLJS Constants took 1071ms
------ WARNING #1 - -----------------------------------------------------------
File: ~/.m2/repository/org/clojure/clojurescript/1.11.60/clojurescript-1.11.60.jar!/cljs/pprint.cljs:260
--------------------------------------------------------------------------------
257 |
258 | (deftype end-block-t :logical-block :start-pos :end-pos)
259 |
260 | (deftype indent-t :logical-block :relative-to :offset :start-pos :end-pos)
--------------------------------------------------------------------------------
variable G__35634__$1 is undeclared
--------------------------------------------------------------------------------
261 |
262 | (def ^:private pp-newline (fn [] "\n"))
263 |
264 | (declare emit-nl)
--------------------------------------------------------------------------------
------ WARNING #2 - -----------------------------------------------------------
File: ~/.m2/repository/missionary/missionary/b.27-SNAPSHOT/missionary-b.27-SNAPSHOT.jar!/missionary/impl.cljs:161:15
--------------------------------------------------------------------------------
158 | (when (== n (alength (.-result j)))
159 | (let [w (.-race j)]
160 | (if (neg? w)
161 | (try ((.-joincb j) (.apply (.-combinator j) nil (.-result j)))
---------------------^----------------------------------------------------------
variable $fexpr__35624 is undeclared
--------------------------------------------------------------------------------
162 | (catch :default e ((.-racecb j) e)))
163 | ((.-racecb j) (aget (.-result j) w)))))))
164 |
165 | (defn race-join [r c ts s f]
--------------------------------------------------------------------------------
nil
<- Closure - Optimizing ... (186481 ms)
In Fly build step on GH I get a “fatal: not a git repository” error. Could that be causing my “required namespace “user” is not available issue?
#22 [build 10/10] RUN clojure -T:build build-client :verbose true :version '"'fdf113d'"'
#22 1.236 Cloning: ;; ...
;; ...
#22 2.582 Checking out: at 55fb6f63ea3cc5344e67e87d2322570d4dddd3d5
#22 12.79 fatal: not a git repository (or any of the parent directories): .git
#22 14.02 Building client. Version: fdf113d
This message seems like a false positive. We have seen it and had no impact. I don’t think it is related to your issue.
How do I debug Reactor failure: Remote error - 1011
?
Finally got my app deployed to Fly, but when I open the app in browser, I see a brief flash of text and then: Remote error - 1011 org.eclipse.jetty.websocket.core.exception.WebSocketException
, which I think means error on server.
The app has not run out of memory on Fly (268MB/496MB) and is still running, but I don’t see any logs on Fly’s monitoring.
First thought is to do a simple (not advanced) client build, because JS stacktrace is inscrutable.
Logback.xml has these two <logger>
entries:
<logger name="hyperfiddle" level="DEBUG" additivity="false"><appender-ref ref="STDOUT" /></logger>
<logger name="hyperfiddle.electric-jetty-adapter" level="WARN" additivity="false"><appender-ref ref="STDOUT" /></logger>
the first thing i do is run the prod build locally to repro outside of fly
i don’t know why it isn’t logging the exception, i don’t think we have seen that issue yet
websocket exception may be different than application exception on server as well, on mobile so can’t look at code path easily for that error
Ran the prod build locally. It starts, throws a different TypeError, but reactor does not crash:
in.3BE3A164204AC9A7A9881BC3DE98B078.js:1699 Uncaught TypeError: Cannot set properties of null (setting 'X')
at B_ (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1699:290)
at p_ (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1705:57)
at o_.o (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1695:291)
at f0 (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1717:77)
at e0 (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1718:31)
at S_ (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1716:390)
at R_.o (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1713:780)
at f0 (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1717:77)
at e0 (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1718:31)
at S_ (main.3BE3A164204AC9A7A9881BC3DE98B078.js:1716:390)
B
This happens even for a simple view that does nothing other than render a static text message (no DB queries).and the dev build works?
Both dev and prod work fine locally. They both throw Only prod throws the TypeError, but it does not seem to cause any problems. I nee to populate the DB in this folder to be 100% sure tho.
I also did prod with :optimizations :simple
- same outcome. Nothing in server logs.
here is the error in dev prod with optimizations :simple
in.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:6943 Uncaught TypeError: Cannot set properties of null (setting 'prev')
at missionary.impl.Reactor.propagate (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:6943:259)
at missionary.impl.Reactor.event (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:6954:168)
at missionary.impl.Reactor.Process.cljs$core$IFn$_invoke$arity$0 (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:6934:245)
at missionary.impl.Ambiguous.cancel (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7009:157)
at missionary.impl.Ambiguous.walk (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7011:85)
at missionary.impl.Ambiguous.kill (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7008:459)
at missionary.impl.Ambiguous.Process.cljs$core$IFn$_invoke$arity$0 (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:6989:374)
at missionary.impl.Ambiguous.cancel (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7009:157)
at missionary.impl.Ambiguous.walk (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7011:85)
at missionary.impl.Ambiguous.kill (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7008:459)
m
@U09K620SG so an interesting thing happens if I start up dev, load up the client (all works as expected in dev - no errors), then I stop dev and start prod while keeping client open (built from same source - should be same hash right? not sure). client then attempts reconnection and sees a reactor failure that might be the same error I’m seeing on Fly, but I can’t be 100% sure and maybe it’s something totally different:
electric_client.cljs:31 WebSocket connection to '' failed:
eval @ electric_client.cljs:31
eval @ Sequential.cljs:29
missionary$impl$Sequential$suspend @ Sequential.cljs:29
eval @ Sequential.cljs:13
missionary$core$park @ core.cljc:162
hyperfiddle$electric_client$connector_$_cr62862_block_0 @ electric_client.cljs:78
eval @ impl.cljc:60
G__59769__0 @ impl.cljc:60
eval @ Sequential.cljs:40
missionary$impl$Sequential$step @ Sequential.cljs:40
missionary$impl$Sequential$run @ Sequential.cljs:59
missionary$core$sp_run @ core.cljc:175
G__59769__3 @ impl.cljc:65
G__46710__2 @ core.cljs:4358
eval @ Sequential.cljs:29
missionary$impl$Sequential$suspend @ Sequential.cljs:29
eval @ Sequential.cljs:13
missionary$core$park @ core.cljc:162
hyperfiddle$electric_client$boot_with_retry_$_cr63073_block_2 @ electric_client.cljs:103
eval @ impl.cljc:60
G__59769__0 @ impl.cljc:60
eval @ Sequential.cljs:40
missionary$impl$Sequential$step @ Sequential.cljs:40
eval @ Sequential.cljs:53
eval @ impl.cljs:205
01:26:08.032 electric_client.cljs:126 Failed to connect.
01:26:08.033 electric_client.cljs:127 Next attempt in 5.5 seconds.
01:26:08.758 websocket.cljs:12 WebSocket connection to '' failed:
shadow$cljs$devtools$client$websocket$start @ websocket.cljs:12
eval @ shared.cljs:324
eval @ shared.cljs:345
01:26:08.758 shared.cljs:305 shadow-cljs - remote-error Event {isTrusted: true, type: 'error', target: WebSocket, currentTarget: WebSocket, eventPhase: 2, …}
eval @ shared.cljs:305
shadow$cljs$devtools$client$shared$remote_error @ shared.cljs:18
eval @ websocket.cljs:29
error (async)
shadow$cljs$devtools$client$websocket$start @ websocket.cljs:26
eval @ shared.cljs:324
eval @ shared.cljs:345
setTimeout (async)
eval @ shared.cljs:341
eval @ shared.cljs:297
shadow$cljs$devtools$client$shared$remote_close @ shared.cljs:17
eval @ websocket.cljs:24
01:26:13.542 electric_client.cljs:107 Connecting...
01:26:13.762 electric_client.cljs:112 Connected.
01:26:13.954 electric_client.cljs:66 WebSocket is already in CLOSING or CLOSED state.
hyperfiddle$electric_client$send_BANG_ @ electric_client.cljs:66
eval @ electric_client.cljs:69
hyperfiddle$electric_client$send_all_$_cr62775_block_1 @ electric_client.cljs:69
eval @ impl.cljc:60
G__59769__0 @ impl.cljc:60
eval @ Ambiguous.cljs:262
missionary$impl$Ambiguous$ready @ Ambiguous.cljs:264
missionary$impl$Ambiguous$boot @ Ambiguous.cljs:28
G__59769__2 @ impl.cljc:64
missionary$impl$Ambiguous$backtrack @ Ambiguous.cljs:34
missionary$impl$Ambiguous$branch @ Ambiguous.cljs:73
missionary$impl$Ambiguous$discard @ Ambiguous.cljs:184
missionary$impl$Ambiguous$done @ Ambiguous.cljs:212
eval @ Ambiguous.cljs:233
missionary$impl$Ambiguous$transfer @ Ambiguous.cljs:237
eval @ Ambiguous.cljs:11
cljs$core$_deref @ core.cljs:688
cljs$core$deref @ core.cljs:1477
eval @ Reduce.cljs:16
eval @ Reduce.cljs:16
missionary$impl$Reduce$transfer @ Reduce.cljs:21
missionary$impl$Reduce$ready @ Reduce.cljs:30
G__56331 @ Reduce.cljs:36
G__57122__1 @ Ambiguous.cljs:334
missionary$impl$Sequential$step @ Sequential.cljs:45
eval @ Sequential.cljs:58
missionary$impl$sleep_cancel @ impl.cljs:201
eval @ impl.cljs:195
missionary$impl$Sequential$kill @ Sequential.cljs:26
eval @ Sequential.cljs:11
missionary$impl$Ambiguous$cancel @ Ambiguous.cljs:92
missionary$impl$Ambiguous$walk @ Ambiguous.cljs:116
missionary$impl$Ambiguous$cancel @ Ambiguous.cljs:102
missionary$impl$Ambiguous$walk @ Ambiguous.cljs:116
missionary$impl$Ambiguous$kill @ Ambiguous.cljs:85
eval @ Ambiguous.cljs:9
eval @ Reduce.cljs:8
missionary$impl$racejoin_cancel @ impl.cljs:153
eval @ impl.cljs:177
eval @ electric_client.cljs:56
01:26:13.954 user.cljs:21 Reactor failure: cljs$core$ExceptionInfo {message: 'Remote error - 1011 …apter$WebSocketPingPongListener$12d400b6 OPEN met', data: {…}, cause: null, name: 'Error', description: undefined, …}cause: nullcolumnNumber: undefineddata: {meta: {…}, cnt: 0, arr: Array(0), __hash: -15128758, cljs$lang$protocol_mask$partition0$: 16647951, …}description: undefinedfileName: undefinedlineNumber: undefinedmessage: "Remote error - 1011 org.eclipse.jetty.websocket.core.exception.WebSocketException: WebSocketAdapter$WebSocketPingPongListener$12d400b6 OPEN met"name: "Error"number: undefinedstack: "Error: Remote error - 1011 org.eclipse.jetty.websocket.core.exception.WebSocketException: WebSocketAdapter$WebSocketPingPongListener$12d400b6 OPEN met\n at new cljs$core$ExceptionInfo ()\n at Function.eval [as cljs$core$IFn$_invoke$arity$3] ()\n at Function.eval [as cljs$core$IFn$_invoke$arity$2] ()\n at eval ()\n at hyperfiddle$electric_client$boot_with_retry_$_cr63073_block_10 ()\n at eval ()\n at Function.G__59769__0 [as cljs$core$IFn$_invoke$arity$0] ()\n at eval ()\n at Object.missionary$impl$Sequential$step [as step] ()\n at eval ()"[[Prototype]]: Errorcljs$core$IPrintWithWriter$: {}cljs$core$IPrintWithWriter$_pr_writer$arity$3: ƒ (obj,writer,opts)toString: ƒ ()constructor: ƒ cljs$core$ExceptionInfo(message,data,cause)[[Prototype]]: Object
eval @ user.cljs:21
missionary$impl$Sequential$step @ Sequential.cljs:45
eval @ Sequential.cljs:53
missionary$impl$Sequential$step @ Sequential.cljs:43
eval @ Sequential.cljs:53
missionary$impl$racejoin_terminated @ impl.cljs:163
eval @ impl.cljs:172
eval @ impl.cljs:178
eval @ electric_client.cljs:56
01:26:13.957 websocket.cljs:12 WebSocket connection to '' failed:
s
So symptoms are:
• deploy to Fly — Reactor failure
• local prod build w/optimizations :simple throw Uncaught TypeError: Cannot set properties of null (setting 'prev')
on startup & inputs throw this on click or type:
in.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7996 Uncaught Error: No matching clause:
at hyperfiddle.electric_dom2.happen (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7996:487)
at cljs.core.swap_BANG_.cljs$core$IFn$_invoke$arity$3 (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:1575:215)
at Function.f [as cljs$core$IFn$_invoke$arity$1] (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:1531:256)
at HTMLInputElement.<anonymous> (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7995:339)
h
• local dev - everything works, no errors.
• no server logs visible throughout. Is there a way to get more logs?once the first low level NPE has happened the app is crashed, no further errors produced from this undefined state are interesting
do you have a git clone url?
I’ll DM (it’s a private repo)
Has anyone seen an error like this in prod when interacting with UI components, even just clicking on input (focus change)?
Uncaught Error: No matching clause:
at hyperfiddle.electric_dom2.happen (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7996:487)
at cljs.core.swap_BANG_.cljs$core$IFn$_invoke$arity$3 (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:1575:215)
at Function.f [as cljs$core$IFn$_invoke$arity$1] (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:1531:256)
at HTMLInputElement.<anonymous> (main.362B9B6A4E66C2E4112AB2DE5B5C5B41.js:7995:339)
h
This function:
(defn happen [s e]
; Todo, we need a buffer (unbounded) to force a nil in between overlapping events to fix race
; Buffer is unbounded because all events matter. (This is sequential unbounded queue)
(case (:status s)
:idle {:status :impulse :event e} ; rising edge
:pending {:status :impulse :event e} ; supersede the outstanding event with a new event
:impulse (assert false "two events in the same frame? that's weird and wrong")))
I assume (:status s)
is nil.for this to happen, iiuc, either the app is already crashed and you interacted again from the crashed state, or you ran into a very subtle electric bug that we call the “when true” bug in which an impossible nil can be observed in situations like (when (not= nil x) (prn x)). i believe the bug is fixed in the next major Electric runtime version that Leo has been working on, we have a workaround for this as well. Please confirm that the app wasn’t already broken when you saw this?
How can I tell if the app has crashed if there are no logs and web requests still get served? Is there maybe a special Electric endpoint that can tell me the state of the reactor?
js error