Does anybody do any work with https://data-star.dev/ ? I found a project where someone was building a fairly full featured library (https://github.com/nakkaya/weave/) around it and am having a lot of fun. Wondering if anybody else has any experience and would like to chat more.
Cool, thanks for the elevator pitch - were you by any chance the guy who gave an interview about datastar on hx-pod?π
I'd actually love to try it for building a simple html table with some interactivity (as in : you can enter data in specific fields - so a bit like a spreadsheet with stronger rules and more logic in the background). Is there a close-enough example for something like that?
I'm the author of Datastar, yes. Yes https://data-star.dev/examples/dbmon
This is Go, but basic idea...
package examples
import (
"fmt"
""
""
. ""
""
"math/rand/v2"
"net/http"
"slices"
"sync"
"time"
)
type dbmonQuery struct {
elapsed time.Duration
query string
}
type dbmonDatabase struct {
name string
queries []dbmonQuery
}
type dbmonDatabases struct {
databases []*dbmonDatabase
}
type dbmonInput struct {
MutationRate int `json:"mutationRate"`
Fps int `json:"fps"`
}
type dbmonSettings struct {
MutationRate int `json:"_mutationRate"`
Fps int `json:"_fps"`
}
func setupDBmon(examplesRouter chi.Router) {
dbs := newDBmonDatabases(6)
mutationRate := 0.5
mu := &sync.RWMutex{}
fps := 144
avgWindow := 20
var settings = func() *dbmonSettings {
return &dbmonSettings{
MutationRate: int(mutationRate * 100),
Fps: fps,
}
}
examplesRouter.Route("/dbmon", func(dbmonRouter chi.Router) {
dbmonRouter.Get("/", func(w http.ResponseWriter, r *http.Request) {
u := auth.UserFromContext(r.Context())
RenderPage(dbmon(u, dbs.databases, 0), w, r)
})
dbmonRouter.Put("/inputs", func(w http.ResponseWriter, r *http.Request) {
input := &dbmonInput{}
if err := datastar.ReadSignals(r, input); err != nil {
sse := datastar.NewSSE(w, r)
sse.ConsoleError(err)
return
}
mu.Lock()
if input.MutationRate >= 0 && input.MutationRate <= 100 {
mutationRate = float64(input.MutationRate) / 100
}
if input.Fps > 0 && input.Fps <= 240 {
fps = input.Fps
}
mu.Unlock()
w.WriteHeader(204)
})
dbmonRouter.Get("/updates", func(w http.ResponseWriter, r *http.Request) {
d := time.Second / time.Duration(fps)
t := time.NewTicker(d)
defer t.Stop()
// Add 10 second timeout
timeout := time.NewTimer(10 * time.Second)
defer timeout.Stop()
avg := 1
renderTime := 0 * time.Second
sse := datastar.NewSSE(w, r, datastar.WithCompression())
for {
select {
case <-r.Context().Done():
return
case <-timeout.C:
// Timeout reached, show "Are you still there?" message
sse.PatchElementTempl(dbmonTimeout())
return
case <-t.C:
t.Reset(time.Second / time.Duration(fps))
mu.Lock()
dbs.randomUpdate(mutationRate)
mu.Unlock()
avg = (avg*(avgWindow-1) + int(renderTime)) / avgWindow
sse.MarshalAndPatchSignals(settings())
now := time.Now()
sse.PatchElementTempl(dbmonApp(dbs.databases, time.Duration(avg)))
renderTime = time.Since(now)
}
}
})
})
}
func randomQuery() dbmonQuery {
elapsed := time.Duration(rand.Float64()*15) * time.Millisecond
query := `SELECT blah from something`
if rand.Float32() < 0.2 {
query = `<IDLE> in transaction`
}
if rand.Float32() < 0.1 {
query = `vacuum`
}
return dbmonQuery{
elapsed: elapsed,
query: query,
}
}
func newDBmonDatabase(format string, args ...any) *dbmonDatabase {
db := &dbmonDatabase{
name: fmt.Sprintf(format, args...),
}
db.update()
return db
}
func (db *dbmonDatabase) update() {
r := rand.N(10) + 6
db.queries = make([]dbmonQuery, r)
for i := range r {
db.queries[i] = randomQuery()
}
slices.SortFunc(db.queries, func(a, b dbmonQuery) int {
return int(a.elapsed - b.elapsed)
})
}
func newDBmonDatabases(n int) *dbmonDatabases {
dbs := &dbmonDatabases{
databases: make([]*dbmonDatabase, n*2),
}
for i := 0; i < n*2; i += 2 {
dbs.databases[i] = newDBmonDatabase("cluster%d", i/2+1)
dbs.databases[i+1] = newDBmonDatabase("cluster%dslave", i/2+1)
}
return dbs
}
func (dbs *dbmonDatabases) randomUpdate(r float64) {
for _, db := range dbs.databases {
if rand.Float64() < r {
db.update()
}
}
}
templ dbmon(u *auth.User, dbs []*dbmonDatabase, renderTime time.Duration) {
@page(u, "dbmon") {
@Demo() {
@dbmonApp(dbs, renderTime)
}
@Heading("HTML")
@HtmlCodeHighlight(`
<div
id="demo"
data-init="@get('/examples/dbmon/updates')"
data-signals:_editing__ifmissing="false"
>
<p>
Average render time for entire page: { renderTime }
</p>
<div role="group">
<label>
Mutation Rate %
<input
type="number"
min="0"
max="100"
value="20"
data-on:focus="$_editing = true"
data-on:blur="@put('/examples/dbmon/inputs'); $_editing = false"
data-attr:data-bind:mutation-rate="$_editing"
data-attr:data-bind:_mutation-rate="!$_editing"
/>
</label>
<label>
FPS
<input
type="number"
min="1"
max="144"
value="60"
data-on:focus="$_editing = true"
data-on:blur="@put('/examples/dbmon/inputs'); $_editing = false"
data-attr:data-bind:fps="$_editing"
data-attr:data-bind:_fps="!$_editing"
/>
</label>
</div>
<table style="table-layout: fixed; width: 100%; word-break: break-all">
<tbody>
<!-- Dynamic rows generated by server -->
<tr>
<td>cluster1</td>
<td style="background-color: var(--_active-color)" class="success">
8
</td>
<td aria-description="SELECT blah from something">
12ms
</td>
<!-- More query cells... -->
</tr>
<!-- More database rows... -->
</tbody>
</table>
</div>
`)
@Heading("Explanation")
<p>
Per a conversation on the discord server there was a desire to port an old React Conf
talk, <a href="">DBMon</a>, to Datastar.
</p>
<p>
The logic is 1:1 but all done on the backend, and since it's Go, it's an interesting
comparison to the SPA based approach. We've limited purely since the site is run on a
free tier server and don't want to be a bad user. If you run the site from source you
can easily 10x the rows without major issues.
</p>
@Subheading("Note")
<p>
If you open your Network tab in DevTools we are leveraging ZSTD compression so the data
rate is relatively low for the contents.
</p>
}
}
templ dbmonApp(dbs []*dbmonDatabase, renderTime time.Duration) {
<div
id="demo"
data-init="@get('/examples/dbmon/updates')"
data-signals:_editing__ifmissing="false"
>
<p>
Average render time for entire page: { renderTime }
</p>
<div role="group">
<label>
Mutation Rate %
<input
type="number"
min="0"
max="100"
value="20"
data-on:focus="$_editing = true"
data-on:blur="@put('/examples/dbmon/inputs'); $_editing = false"
data-attr:data-bind:mutation-rate="$_editing"
data-attr:data-bind:_mutation-rate="!$_editing"
/>
</label>
<label>
FPS
<input
type="number"
min="1"
max="144"
value="60"
data-on:focus="$_editing = true"
data-on:blur="@put('/examples/dbmon/inputs'); $_editing = false"
data-attr:data-bind:fps="$_editing"
data-attr:data-bind:_fps="!$_editing"
/>
</label>
</div>
<table style="table-layout: fixed; width: 100%; word-break: break-all">
<tbody>
for _, db := range dbs {
{{
count := len(db.queries)
}}
<tr>
<td>{ db.name }</td>
<td
style="background-color: var(--_active-color)"
if count >= 15 {
class="error"
} else {
if count >= 10 {
class="warning"
} else {
class="success"
}
}
>
{ count }
</td>
for _, q := range db.queries[:5] {
<td aria-description={ q.query }>
{ q.elapsed }
</td>
}
</tr>
}
</tbody>
</table>
</div>
}
templ dbmonTimeout() {
<div id="demo" class="text-center">
<h2>Are you still there?</h2>
<p>Updates have been paused to save bandwidth and reduce COβ emissions.</p>
<p class="text-sm text-gray-600">We don't want to waste resources if you've stepped away!</p>
<button
class="btn btn-primary"
data-on:click="window.location.reload()"
>
Refresh to resume
</button>
</div>
}Sweet, thanks!
Btw I really enjoyed the interview:)
thanks. D* makes it really easy just send down a new copy of your table when you want... that's it
D* is an FRP engine for the web done right imo
We do. We have built one project and we have started a new one in Jan.
@asier.galdos very interesting. how can I go there?
https://checkboxes.andersmurphy.com/ https://andersmurphy.com/2025/04/07/clojure-realtime-collaborative-web-apps-without-clojurescript.html https://andersmurphy.com/2025/04/15/why-you-should-use-brotli-sse.html https://andersmurphy.com/about https://github.com/andersmurphy/hyperlith There's should also be some conj talks coming out soon that cover how we use it at work.
I don't use weave directly, but it's a really nice way to get started with datastar.
let me check
Neat! I'm building a little app using weave and it's pretty nifty. I wonder if we should have a channel for it?
Great!
do you need some help from me? @stephen676
I am very interesting to build some app using weave. π
Well it's a spare time project and likely to become open source so it would have to be for fun. If you want to ensemble with us I can ask the other guy I've been pairing with. He and I meet again this Saturday.
ok, thanks @stephen676
One thing I can't find at all is any example of doing authentication using openid connect or even webauthn. Seems like a bit of a pain? I'm sure once I've nailed down the encoding/decoding situation all will be well. But boy do I wish for someone to have solved this already π
@stephen676. yeah, right. most frameworks don't include OIDC/WebAuthn because they're complex standards.
I can help you.
I think encoding/decoding you mentioned is usually handled by these libraries.
passport , passport-openidconnect , express-session
am I right?
authentication is a pain point! Many developers feel the same way. π
Yeah those work for more traditional JavaScript-based apps. Not so much for server-side rendered. Unless of course we move the boundary to more client-side for the auth piece. Was hoping to avoid JavaScript as much as possible
hmmm. Which framework do you prefer, then?
Well I'm working with weave which is based on datastar
It's super new.
I have the feeling we are at a wild frontier .. fun and scary π€£
I am also enjoy like that. π
shall we work on together?
I've heard good things about Datastar, careful though... it's a cult
Yeah the hardest part has been convincing it to do things it wasn't really designed to do. The boundary between server-side and client-side is so fuzzy...
If you have specifics sure @andersmurphy or i can help
Sweet.. thanks! What would you think of having a #data-star channel?
I mean there is already a #data-clojure on the D* discord. but up to y'all
Oh!
I go look there π
π
me, too
@delaneygillilan I can't #data-clojure channel. I am not sure I am in right place on the discord.
https://discord.com/channels/1296224603642925098/1333801016642244739
let me check.
can you send me the server name?
Great!!!
From what I understood it's basically htmx but with oob-swap as default behavior. I am really curious whether it can make hypermedia stuff even simpler than htmx but I haven't found the motivation to move out of my htmx comfort zone so far
It not like that at all... Well it does have some similarity from the outside but very different core
It's a unified framework, not a library. Most pages need zero JS Fastest signals/reactivity of any JS framework Fastest morph (10-30x faster than idiomorph) You can change any part of any page at any time (works great with CQRS/datalog style dev) SDKs for 14+ languages including streaming 40-300x compression in most SDKs Plugin system built like a game engine, the core is < 500 loc and everything else (even SSE) is a plugin 11kb, which is smaller than HTMX or Alpine or hyperscript... but does more than all 3 Spec compliant HTML for everything Has been proven out to go VERY well with CLJ ideals, playes perfectly with hiccup and is an immediate mode engine for left fold of state Built by a game engine programmer that hates JS but loves Lisp (even if he doesn't use it) Modular so you can take out any part or do your own thing Actively dogfooded (HTMX website is a SPA for example) on production apps. More actively developed than HTMX
They look the same until you try both or measure π
If you have specific questions @aaronrebmann i'm happy to answer. Clojure is my favorite language I don't actively use
https://m.youtube.com/watch?v=Ozipf13jRr4 I've enjoyed βThinking Allowedβ in the past and I totally forgot that the host of this show got the opportunity to interview John McCarthy. Pretty cool to get to listen to our lisp grandpa talk about ai and stuff. I watched this hoping he would go over KIF but I think this may have been even earlier than 1992.
The one I saw most recently was on synchronicity
Mishlove is great
Me too! I think the first episode I saw was with Terrance McKenna but there have been many amazing guests and the interviews are always very enlightening. Do you have a favorite episode?
Love that show