Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
255 commits
Select commit Hold shift + click to select a range
a12f3d5
Update datastar latest
andersmurphy Jan 27, 2025
78d085b
Add max-refresh-ms setting
andersmurphy Jan 30, 2025
adc0679
Only send sse event if view changes
andersmurphy Jan 31, 2025
4991d3c
Only convert html to string if it is being sent
andersmurphy Jan 31, 2025
f42cc96
Make refresh events sequential ticks
andersmurphy Feb 1, 2025
c35c61f
Clean up some formatting
andersmurphy Feb 1, 2025
04e38f5
Update datastar to beta3
andersmurphy Feb 2, 2025
daf8faa
Add streaming gzip for SSE
andersmurphy Feb 3, 2025
3c5c4be
Update some comments
andersmurphy Feb 4, 2025
8a32d70
Handle work cancelling
andersmurphy Feb 4, 2025
528b099
Update readme
andersmurphy Feb 4, 2025
f9cb2fe
Update formatting
andersmurphy Feb 4, 2025
6e1554f
Add asset support with hashed names
andersmurphy Feb 4, 2025
459be5c
Simple css handling with reload
andersmurphy Feb 5, 2025
c46b2f4
Move icons to own file and and helper for css variables
andersmurphy Feb 5, 2025
df2f31d
Fix data-bind ordering
andersmurphy Feb 5, 2025
f3bbcb3
Revert to older version of datastar
andersmurphy Feb 5, 2025
79ff82d
Bump datastar with latest idiomorph changes
andersmurphy Feb 5, 2025
1e5cfec
Update README.md
andersmurphy Feb 6, 2025
04b40c9
Update readme with links to datastar
andersmurphy Feb 7, 2025
4b007b5
Update datastar version
andersmurphy Feb 10, 2025
3b057be
Add presence cursor indicator example
andersmurphy Feb 10, 2025
8dfa38d
Add support for query params
andersmurphy Feb 10, 2025
48a0cdd
Add some todos
andersmurphy Feb 10, 2025
5e37c36
Remove completed todo
andersmurphy Feb 10, 2025
97b394e
Allow adding arbitrary html to head
andersmurphy Feb 10, 2025
a78e371
Pass query params to updates
andersmurphy Feb 10, 2025
7829113
Remove icons and some helper functions
andersmurphy Feb 11, 2025
0bff3ca
Update README.md
andersmurphy Feb 11, 2025
0b7709c
Add csrf to request so handlers can use it
andersmurphy Feb 11, 2025
5084669
Add SSE id hash for reducing network on reconnects
andersmurphy Feb 12, 2025
84afc5b
Fix issue with csrf
andersmurphy Feb 12, 2025
e080f90
Fix another edge case well clearing cookies
andersmurphy Feb 12, 2025
7aa398b
Bump to datastar beta6
andersmurphy Feb 13, 2025
d1a37ee
Add some comments around 403
andersmurphy Feb 13, 2025
ee9b7be
Switch to chassis for html generation
andersmurphy Feb 14, 2025
0bb8b8b
Bump datastar to beta7
andersmurphy Feb 14, 2025
baf7109
Add merge signals for use in actions
andersmurphy Feb 15, 2025
622dfc2
Fix some typos in the readme
andersmurphy Feb 15, 2025
88dd67f
Add simple cache
andersmurphy Feb 16, 2025
19ad37d
Update example to use cache
andersmurphy Feb 16, 2025
3c9c852
Update comment
andersmurphy Feb 16, 2025
d44a21c
Add caching to presence cursors
andersmurphy Feb 16, 2025
8a24837
Add example caddy file for local https
andersmurphy Feb 16, 2025
3c3927c
Add comments around work sharing and caching
andersmurphy Feb 17, 2025
40c5d2b
Update comments around event filtering
andersmurphy Feb 18, 2025
3839406
Add get!/post!
andersmurphy Feb 18, 2025
383a30a
Go full potemkin
andersmurphy Feb 18, 2025
6e6e2e5
Vendor import-vars
andersmurphy Feb 18, 2025
b43082c
Add trace helpers
andersmurphy Feb 18, 2025
50c990f
Add debug-signal helper
andersmurphy Feb 19, 2025
007acd3
Handle raw css string
andersmurphy Feb 19, 2025
39953fa
Change resource loading code to work with bytes
andersmurphy Feb 19, 2025
c41860a
Only special case vectors for css
andersmurphy Feb 19, 2025
8133960
Fix issue with adding elements to shim head
andersmurphy Feb 19, 2025
e8aee94
Simplify shim updates connection code
andersmurphy Feb 19, 2025
54cc2d1
Simplify cursor example
andersmurphy Feb 20, 2025
9bfd4d4
Example layout tweaks
andersmurphy Feb 20, 2025
a7b935b
Add some helper functions
andersmurphy Feb 20, 2025
7fc8fa5
Fix flash on initial load
andersmurphy Feb 20, 2025
cd9b8d4
Add drag and drop example
andersmurphy Feb 20, 2025
240692a
Make drag drop example deployable as a jar
andersmurphy Feb 21, 2025
ea99fbb
Readme and instructions for example server set up
andersmurphy Feb 21, 2025
4ca359f
Streamlining for in production example
andersmurphy Feb 21, 2025
3411dac
Add a score and instructions
andersmurphy Feb 21, 2025
7184e07
Fix drag and drop on ios (issue with emoji selection)
andersmurphy Feb 21, 2025
3a2b8ea
Handle remote css update flash
andersmurphy Feb 22, 2025
699986e
Move examples to their own sub packages
andersmurphy Feb 22, 2025
5232da2
Add env and example .env.edn
andersmurphy Feb 22, 2025
a8e4ba8
Add on refresh handler
andersmurphy Feb 22, 2025
44a1ef5
Update todo
andersmurphy Feb 22, 2025
c4e5858
Make drag and drop responsive
andersmurphy Feb 22, 2025
09a06a2
Update README.md
andersmurphy Feb 23, 2025
798f8e9
Example layout changes
andersmurphy Feb 23, 2025
721bed7
Improve example user experience on mobile
andersmurphy Feb 23, 2025
0868aad
Add link to source
andersmurphy Feb 24, 2025
e5de168
Update README.md
andersmurphy Feb 24, 2025
36f017d
Add popover example
andersmurphy Feb 25, 2025
3f2025f
Add commutative connected counter example
andersmurphy Feb 25, 2025
e2bee6e
Update caddyfile
andersmurphy Feb 26, 2025
a89da1d
Add simple AI
andersmurphy Feb 26, 2025
0c59291
Support custom state keys
andersmurphy Feb 27, 2025
1891907
Handle closing database in datalevin example
andersmurphy Feb 27, 2025
71829d4
Update to datastar beta9
andersmurphy Mar 4, 2025
bfdbb67
Make only fail if a key is missing, not if there is no .env.edn file
andersmurphy Mar 5, 2025
164a397
Only parse json if response it application/json
andersmurphy Mar 5, 2025
f76351d
Add gunzip
andersmurphy Mar 5, 2025
e76b23e
First draft of http-kit sse client
andersmurphy Mar 5, 2025
42048b3
Simplify SSE parsing
andersmurphy Mar 6, 2025
9cdf9de
Remove unused function
andersmurphy Mar 6, 2025
f256dfb
Fix reflection warning
andersmurphy Mar 6, 2025
33b1183
Fix encoding on gunzip and move sse http code into load testing ns
andersmurphy Mar 7, 2025
a241cc7
Refactor to use more clojure.java.io helpers
andersmurphy Mar 7, 2025
a093fd8
Remove AI
andersmurphy Mar 8, 2025
c1c6809
Fix db access in example
andersmurphy Mar 8, 2025
9e0ba3e
Add brotli
andersmurphy Mar 8, 2025
b443b97
Make brotli the default
andersmurphy Mar 8, 2025
bd578c5
Remove gzip
andersmurphy Mar 8, 2025
52441f2
Update README.md
andersmurphy Mar 8, 2025
af827db
Clean up examples
andersmurphy Mar 8, 2025
39abe77
Fix brotli on server
andersmurphy Mar 8, 2025
63c5151
Add support for non linux 86 dev environments (no windows)
andersmurphy Mar 13, 2025
665bad2
Add comment about brotli buffer size
andersmurphy Mar 13, 2025
bb6c41e
Test datastar without settle mechanic
andersmurphy Mar 13, 2025
a86c494
Fix broken import
andersmurphy Mar 15, 2025
6ac6931
Add etag support for shims
andersmurphy Mar 15, 2025
ba9b68a
Update readme
andersmurphy Mar 17, 2025
93ba249
Test rocket signals datastar
andersmurphy Mar 18, 2025
6adf208
Add optional batching mechanism
andersmurphy Mar 21, 2025
b15c929
Clean up batch function
andersmurphy Mar 22, 2025
651b945
Remove batch logic
andersmurphy Mar 22, 2025
770bb3f
Fix some readme typos
andersmurphy Mar 22, 2025
59fa451
Make load testing decompression accumulative
andersmurphy Mar 22, 2025
9c172ef
transact! -> tx!
andersmurphy Mar 22, 2025
3b388b6
Add a tuples function so datalog can be used to query edn
andersmurphy Mar 23, 2025
743572b
Clean up namespace keys
andersmurphy Mar 24, 2025
077b3ce
Rename state -> ctx
andersmurphy Mar 24, 2025
d198db2
Remove unnecessary import
andersmurphy Mar 24, 2025
1d6a85f
Add function for getting current app useful for prod repl
andersmurphy Mar 24, 2025
422f574
Add error handling that supports custom logging
andersmurphy Mar 26, 2025
dbaab32
Update readme
andersmurphy Mar 26, 2025
57de0e3
Add a comment about error trace trimming
andersmurphy Mar 27, 2025
71d7c03
Improve error handling
andersmurphy Mar 27, 2025
ae0d773
Process error messages with transducers and add demunge
andersmurphy Mar 27, 2025
ce21c92
Generate id for errors
andersmurphy Mar 27, 2025
465ab4f
Close connection on error in render-fn
andersmurphy Mar 28, 2025
70bc457
Handle try-log returns nil on error
andersmurphy Mar 28, 2025
62fdfff
Make try-log accessible in user land for use in futures etc
andersmurphy Mar 28, 2025
90c9e59
Don't dissoc via (this can be done in user land)
andersmurphy Mar 29, 2025
c03a2ff
Cleaner stack traces
andersmurphy Mar 29, 2025
831dd9d
Fix typo
andersmurphy Mar 29, 2025
bad4376
More trimming
andersmurphy Mar 29, 2025
09eb7a9
Disable remove invoke for now
andersmurphy Mar 29, 2025
9b952db
Add dedupe-with
andersmurphy Mar 29, 2025
e9eabc1
Regex based stack trace filtering
andersmurphy Mar 29, 2025
4ee8bea
Regex good
andersmurphy Mar 29, 2025
eb6ba9b
Add some more namespaces to filter
andersmurphy Mar 29, 2025
bdfe191
Make content-type check more robust
andersmurphy Mar 29, 2025
d3345fe
Demunge anonymous functions
andersmurphy Mar 29, 2025
b4b13eb
Exclude binding conveyor from errors
andersmurphy Mar 29, 2025
50973c8
Make error messages more readable
andersmurphy Mar 29, 2025
b206dea
Add type to error
andersmurphy Mar 29, 2025
a030a34
Add extras (these are optional modules/plugins like datalog)
andersmurphy Mar 30, 2025
a0cc5cf
Port datalevin example to use default schema
andersmurphy Mar 30, 2025
e2f8796
Handle non serialisable error data
andersmurphy Mar 30, 2025
e5456fc
reverse first -> peek
andersmurphy Mar 30, 2025
f52bc02
Move tuples into datalog namespace
andersmurphy Mar 31, 2025
6ab4d17
Remove unused namespace
andersmurphy Mar 31, 2025
f695acc
Bump to datastar v1.0.0-beta.11
andersmurphy Mar 31, 2025
6c1cecf
Add cause to error-id hash
andersmurphy Mar 31, 2025
1327cce
Tracing only capture traces that have not already been seen
andersmurphy Mar 31, 2025
6ff5077
Recorded -> logged
andersmurphy Mar 31, 2025
ddd34b1
Rename trace function
andersmurphy Mar 31, 2025
cac66cc
Add wrap routes
andersmurphy Apr 1, 2025
622b75a
Update readme with goals
andersmurphy Apr 1, 2025
a6d7cc3
Update readme with copy
andersmurphy Apr 1, 2025
5bd04eb
Update goals
andersmurphy Apr 1, 2025
d5d6254
Add form validation example
andersmurphy Apr 2, 2025
8caacfd
Rename datalog extra to datalevin as I plan to explore asami too
andersmurphy Apr 3, 2025
962f938
Add throw-if-status-not!
andersmurphy Apr 3, 2025
cc5f1ac
Remove ring codec as a dependency (only need query param parsing)
andersmurphy Apr 3, 2025
070086b
Fix issue form-decode
andersmurphy Apr 3, 2025
55d2477
Add game of life demo
andersmurphy Apr 4, 2025
e66b471
Add link to source
andersmurphy Apr 4, 2025
be7eaa7
Fix typo
andersmurphy Apr 4, 2025
2f9d7bc
Add css ease animation
andersmurphy Apr 5, 2025
3d05a47
Only transition background
andersmurphy Apr 5, 2025
1e6f100
Support combining multiple css sources
andersmurphy Apr 5, 2025
fecc80a
Make thread macro accessible
andersmurphy Apr 5, 2025
1618955
Add modulo pick
andersmurphy Apr 5, 2025
bb2eb26
Use correct namespace
andersmurphy Apr 5, 2025
611ac1e
Simple colours
andersmurphy Apr 5, 2025
2da8763
Add other colors
andersmurphy Apr 6, 2025
7bd6bf1
Match refresh rate and simulation rate
andersmurphy Apr 6, 2025
9e028f5
Change from on click to mousedown
andersmurphy Apr 6, 2025
1ab9a72
Clean up readme
andersmurphy Apr 6, 2025
e105cff
Right size brotli window for longer streams
andersmurphy Apr 7, 2025
6f140db
Fix color in example
andersmurphy Apr 8, 2025
a6e85b7
Expose br-window-size in render handlers
andersmurphy Apr 8, 2025
2236056
Clean up session code
andersmurphy Apr 8, 2025
9858923
Leverage bubble up event handling
andersmurphy Apr 8, 2025
d4dda3c
Bump deps
andersmurphy Apr 8, 2025
67ec85f
Bump deps
andersmurphy Apr 11, 2025
69c9cca
Add render handler for datastar site embed
andersmurphy Apr 14, 2025
d82e3cb
Add circular-subvec
andersmurphy Apr 14, 2025
0acb56d
Separate example for less minimal game of life
andersmurphy Apr 14, 2025
8e9f1ca
Allow rendering of smaller game board for datastar site
andersmurphy Apr 14, 2025
f8e3a91
406 clients that don't handle brotli
andersmurphy Apr 14, 2025
43b76d1
Revert "Support combining multiple css sources"
andersmurphy Apr 15, 2025
21148ba
Bump deps
andersmurphy Apr 15, 2025
9d90d17
Expose chassis resolve alias
andersmurphy Apr 17, 2025
a7a3d10
Work in progress virtual scroll
andersmurphy Apr 18, 2025
fda4e15
Explore different approach to virtual scroll
andersmurphy Apr 21, 2025
95532c5
Update readme
andersmurphy Apr 21, 2025
ac6bb26
Add linebreak
andersmurphy Apr 21, 2025
4cf86ab
Clean up readme
andersmurphy Apr 21, 2025
912cbb8
Fix typo
andersmurphy Apr 21, 2025
151e600
More typos
andersmurphy Apr 21, 2025
463c354
Bump datastar to RC5
andersmurphy Apr 21, 2025
942a051
Virtual scroll game of life
andersmurphy Apr 21, 2025
9fb8beb
Remove unused css class
andersmurphy Apr 21, 2025
2e81f04
Trim headers on actions
andersmurphy Apr 21, 2025
8ffc316
Improve performance of GoL
andersmurphy Apr 21, 2025
53c70f0
Increase size
andersmurphy Apr 21, 2025
b10e55b
Add plissken mode
andersmurphy Apr 22, 2025
8bda443
Add one million checkboxes example
andersmurphy Apr 22, 2025
d1d5bec
Remove trace code
andersmurphy Apr 22, 2025
c7b3cab
One million checkboxes almost ready to go
andersmurphy Apr 22, 2025
432dd83
Fix refresh hashing
andersmurphy Apr 22, 2025
a3b38b0
Not using caching
andersmurphy Apr 22, 2025
44f202d
Remove checkbox optimistic updates
andersmurphy Apr 22, 2025
6e8341a
Add first render flag to render-handler data
andersmurphy Apr 24, 2025
91f69d4
Switch to pointerdown (snappier on mobile for some reason)
andersmurphy May 3, 2025
86da678
Revert GoL to original version
andersmurphy May 3, 2025
c2c3e24
Update readme
andersmurphy May 3, 2025
f6e4c5d
Lift up variables to ensure chassis compiles properly
andersmurphy May 3, 2025
7dd2cb8
Improve digest performance
andersmurphy May 4, 2025
10a4c92
Tune rendering (creating strings in loops is slow)
andersmurphy May 4, 2025
716b8a0
Fast checkboxes
andersmurphy May 4, 2025
831bed2
Make digest consistent between JVM executions
andersmurphy May 5, 2025
f5429cf
Remove form validation example
andersmurphy May 5, 2025
5086db1
More naive checkboxes
andersmurphy May 5, 2025
49374a7
Removed unused str in example
andersmurphy May 6, 2025
95ce21b
Chunked checkboxes
andersmurphy May 6, 2025
510f495
Add tabid
andersmurphy May 7, 2025
7e38ef0
Handle tab state
andersmurphy May 7, 2025
c0bf617
Make the virtual scroll less chatty
andersmurphy May 7, 2025
673ec82
Ten million checkboxes
andersmurphy May 7, 2025
bd5c9e1
Clean up ten million checkboxes example
andersmurphy May 7, 2025
2efffe4
Add datalevin link
andersmurphy May 7, 2025
eeff525
One billion checkboxes
andersmurphy May 12, 2025
dfdfff8
Clean up server set up
andersmurphy May 12, 2025
c3957b9
RC7
andersmurphy May 14, 2025
f8aa0b2
Add chunk-id halves layout shift time
andersmurphy May 14, 2025
91c2e36
Remove some dead code
andersmurphy May 14, 2025
ac68fd7
Add some repl server test
andersmurphy May 14, 2025
fe1e6fe
Only draw check when box checked
andersmurphy May 16, 2025
ed49d2c
Add sql migrations as idempotent function
andersmurphy May 16, 2025
4fe9ecc
Change connection pool size
andersmurphy May 19, 2025
c2dbf66
Make IP configurable
coreagile May 21, 2025
b145c81
Add additional bench code
andersmurphy May 25, 2025
7b1e237
Move to sqlite4clj
andersmurphy May 27, 2025
525c9f0
Bump sqlite4clj
andersmurphy May 28, 2025
862c135
More FFI flags
andersmurphy May 30, 2025
d424ce3
Catch and log errors happening in a batch, remove max size on batch
andersmurphy May 30, 2025
6b80522
Clean up error logging
andersmurphy May 30, 2025
c194ae1
Remove some dead code in demo
andersmurphy May 30, 2025
11084f0
Bump sqlite4clj
andersmurphy May 30, 2025
4fa4989
Bump sqlite4clj
andersmurphy May 30, 2025
236f846
Merge branch 'andersmurphy:master' into master
coreagile May 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/db/
**/db/
**/database.db*
.*
!.github
!.gitignore
Expand Down
20 changes: 20 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Caddy file for testing with https locally
#
# You can install Caddy with:
#
# $ brew install caddy
#
# You can start caddy with in the project root directory:
#
# $ Caddy run
#
# Then start your project at the server at repl on port 8080
#
localhost:3030 {
reverse_proxy localhost:8080 {
# If localhost:8080 is not responding retry every second for
# 30 seconds. This stops deployments from breaking SSE connections.
lb_try_duration 30s
lb_try_interval 1s
}
}
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Anders Murphy

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
138 changes: 136 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,138 @@
# Hyperlith: the hypermedia based monolith

This is the start of a very opinionated fullstack datastar library mostly
for personal use.
This is a small and very opinionated fullstack [Datastar](https://data-star.dev/) framework mostly
for my personal use. However, I felt there were some ideas in here that were worth sharing.

Hyperlith only uses a subset of Datastar's feartures. If you want a production ready full featured Datastar Clojure SDK use [the official SDK](https://github.com/starfederation/datastar/tree/main/sdk/clojure).

>⚠️ **WARNING:** API can change at any time! Use at your own risk.

## Goals / Priorities

- **Opinionated** - does not try to be a general solution.
- **Server driven** - push based multiplayer and streaming updates from day 1.
- **Production REPL first** - a running system should be simple to: introspect, modify and patch.
- **Minimal dependencies** - ensures the project is robust and secure long term.
- **Operationally Simple** - handles all operational tasks in process.
- **Sovereign** - can be deployed on any VPS provider.

## F.A.Q

**Q:** *Why do I get 403 when running the examples locally in Chrome or Safari?*

**A:** *The session and csrf cookies use `Secure` this means that these cookies won't be set on localhost when using chrome or safari (as they require HTTPS) . If you want to use Chrome or Safari for local development you can run `caddy run` to start a local https proxy on https://localhost:3030. The local `caddyfile` can be [found here](https://github.com/andersmurphy/hyperlith/blob/master/Caddyfile). The goal is for development to be as close to production as possible, and Hyperlith is designed to be run behind a reverse proxy.*

## Rational (more like a collection of opinions)

#### Why large/fat/main morphs (immediate mode)?

By only using `data: mergeMode morph` and always targeting the `main` element of the document the API can be massively simplified. This avoids having the explosion of endpoints you get with HTMX and makes reasoning about your app much simpler.

#### Why have single render function per page?

By having a single render function per page you can simplify the reasoning about your app to `view = f(state)`. You can then reason about your pushed updates as a continuous signal rather than discrete event stream. The benefit of this is you don't have to handle missed events, disconnects and reconnects. When the state changes on the server you push down the latest view, not the delta between views. On the client idiomorph can translate that into fine grained dom updates.

#### Why re-render on any database change?

When your events are not homogeneous, you can't miss events, so you cannot throttle your events without losing data.

But, wait! Won't that mean every change will cause all users to re-render? Yes, but at a maximum rate determined by the throttle. This, might sound scary at first but in practice:

- The more shared views the users have the more likely most of the connected users will have to re-render when a change happen.

- The more events that are happening the more likely most users will have to re-render.

This means you actually end up doing more work with a non homogeneous event system under heavy load than with this simple homogeneous event system that's throttled (especially it there's any sort of common/shared view between users).

#### Why no diffing?

In theory you can optimise network and remove the need for idiomorph if you do diffing between the last view and the current view. However, in practice because the SSE stream is being compressed for the duration of a connection and html compresses really well you get amazing compression (reduction in size by 90-100x! Sometimes more) over a series of view re-renders. The compression is so good that in my experience it's more network efficient and more performant that fine grained updates with diffing (without any of the additional complexity).

This approach avoids the additional challenges of view and session maintenance (increased server load and memory usage).

My suspicion is websocket approaches in this space like Phoenix Liveview haven't stumbled across this because you don't get compression out of the box with websockets, and idiomorph is a relatively new invention. Intuitively you would think the diffing approach would be more performant so you wouldn't even consider this approach.

#### Signals are only for ephemeral client side state

Signals should only be used for ephemeral client side state. Things like: the current value of a text input, whether a popover is visible, current csrf token, input validation errors. Signals can be controlled on the client via expressions, or from the backend via `merge-signals`.

#### Signals in fragments should be declared __ifmissing

Because signals are only being used to represent ephemeral client state that means they can only be initialised by fragments and they can only be changed via expressions on the client or from the server via `merge-signals` in an action. Signals in fragments should be declared `__ifmissing` unless they are "view only".

#### View only signals

View only signals, are signals that can only be changed by the server. These should not be declared `__ifmissing` instead they should be made "local" by starting their key with an `_` this prevents the client from sending them up to the server.

#### Actions should not update the view themselves directly

Actions should not update the view via merge fragments. This is because the changes they make would get overwritten on the next `render-fn` that pushes a new view down the updates SSE connection. However, they can still be used to update signals as those won't be changed by fragment merges. This allows you to do things like validation on the server.

#### Stateless

The only way for actions to affect the view returned by the `render-fn` running in a connection is via the database. The ensures CQRS. This means there is no connection state that needs to be persisted or maintained (so missed events and shutdowns/deploys will not lead to lost state). Even when you are running in a single process there is no way for an action (command) to communicate with/affect a view render (query) without going through the database.

#### CQRS

- Actions modify the database and return a 204 or a 200 if they `merge-signals`.
- Render functions re-render when the database changes and send an update down the updates SSE connection.

#### Work sharing (caching)

Work sharing is the term I'm using for sharing renders between connected users. This can be useful when a lot of connected users share the same view. For example a leader board, game board, presence indicator etc. It ensures the work (eg: query and html generation) for that view is only done once regardless of the number of connected users.

There's a lot of ways you can do this. I've settled on a simple cache that gets invalidate when a `:refresh-event` is fired. This means the cache is invalidated at most every X msec (determined by `:max-refresh-ms`) and only if the db state has changed.

To add something to the cache wrap the function in the `cache` higher order function.

#### Batching

Batching pairs really well with CQRS as you have a resolution window, this defines the maximum frequency the view can update, or in other terms the granularity/resolution of the view. Batching can generally be used to improve throughput by batching changes.

However, there is one downsides with batching to keep in mind and that is you don't get atomic transactions. The transaction move to the batch level, not the transact/insert level. Transaction matter when you are dealing with constraints you want to deal with at the database level, classic example is accounting systems or ledgers where you want to be able to fail an atomic transaction that violates a constraint (like user balance going negative). The problem with batching is that that transaction constraint failure, fails the whole batch not only the transact that was culpable.

#### Use `data-on-pointerdown/mous` over `data-on-click`

This is a small one but can make even the slowest of networks feel much snappier.

## Other Radical choices

#### No CORS

By hosting all assets on the same origin we avoid the need for CORS. This avoids additional server round trips and helps reduce latency.

#### Cookie based sessions

Hyperlith uses a simple unguessable random uid for managing sessions. This should be used to look up further auth/permission information in the database.

#### CSRF

Double submit cookie pattern is used for CSRF.

#### Rendering an initial shim

Rather than returning the whole page on initial render and having two render paths, one for initial render and one for subsequent rendering a shell is rendered and then populated when the page connects to the updates endpoint for that page. This has a few advantages:

- The page will only render dynamic content if the user has javascript and first party cookies enabled.

- The initial shell page can generated and compressed once.

- The server only does more work for actual users and less work for link preview crawlers and other bots (that don't support javascript or cookies).

#### Routing

Router is a simple map, this means path parameters are not supported use query parameters or body instead. I've found over time that path parameters force you to adopt an arbitrary hierarchy that is often wrong (and place oriented programming). Removing them avoids this and means routing can be simplified to a map and have better performance than a more traditional adaptive radix tree router.

>📝 Note: The Hyperlith router is completely optional and you can swap it out for reitit if you want to support path params.

#### Reverse proxy

Hyperlith is designed to be deployed between a reverse proxy like caddy for handling HTTP2/3 (you want to be using HTTP2/3 with SSE).

#### Minimal middleware

Hyperlith doesn't expose middleware and keeps the internal middleware to a minimum.

#### Minimal dependencies

Hyperlith tries to keep dependencies to a minimum.
52 changes: 30 additions & 22 deletions deps.edn
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
{:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.12.0"}
org.clojure/data.json {:mvn/version "2.5.1"}
;; http
http-kit/http-kit
{:git/url "https://github.com/http-kit/http-kit"
:git/sha "76b869fc34536ad0c43afa9a98d971a0fc32c644"}
;; templating
hiccup/hiccup
{:git/url "https://github.com/weavejester/hiccup"
:git/sha "24d55d1c7c1fc3c249395d261eb723c381843bc0"}
;; db
datalevin/datalevin {:mvn/version "0.9.17"}}
:aliases {:dev {:extra-paths ["dev"]
:jvm-opts
["-Duser.timezone=UTC"
"-XX:+UseZGC"
;; ZGenerational will be the default in future
;; so this won't need to be specified
"-XX:+ZGenerational"
"--add-opens=java.base/java.nio=ALL-UNNAMED"
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"]}}}
{:paths ["src" "resources"]
:deps
{org.clojure/clojure {:mvn/version "1.12.0"}
org.clojure/data.json {:mvn/version "2.5.1"}
org.clojure/core.async {:mvn/version "1.7.701"}

;; HTTP
http-kit/http-kit {:mvn/version "2.9.0-beta1"}

;; COMPRESSION
com.aayushatharva.brotli4j/brotli4j {:mvn/version "1.18.0"}
;; Assumes you deploy uberjar on linux x86_64
com.aayushatharva.brotli4j/native-linux-x86_64 {:mvn/version "1.18.0"}
io.netty/netty-buffer {:mvn/version "4.1.119.Final"}

;; TEMPLATING
dev.onionpancakes/chassis {:mvn/version "1.0.365"}

;; SQLITE
andersmurphy/sqlite4clj
{:git/url "https://github.com/andersmurphy/sqlite4clj"
:git/sha "c3083c0448cd0cedb4d6b0a497f3dea05334a2f4"}
com.github.seancorfield/honeysql {:mvn/version "2.7.1295"}}
:aliases
{:dev
{:extra-paths ["dev"]
:extra-deps
{com.aayushatharva.brotli4j/native-osx-x86_64 {:mvn/version "1.18.0"}
com.aayushatharva.brotli4j/native-osx-aarch64 {:mvn/version "1.18.0"}
com.aayushatharva.brotli4j/native-linux-aarch64 {:mvn/version "1.18.0"}}}}}
55 changes: 0 additions & 55 deletions dev/example/main.clj

This file was deleted.

17 changes: 0 additions & 17 deletions dev/example/schema.clj

This file was deleted.

2 changes: 2 additions & 0 deletions dev/scratch.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
(ns scratch
(:require [hyperlith.core :as h]))
57 changes: 57 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Server setup and management

## Add a record to DNS

Add an A record @ which points to the IPV4 address of the VPS. For IPV6 add a AAAA record @ which points to the IPV6 address of the VPS.

## Initial server setup

Move the setup script to the server:

```bash
scp server-setup.sh [email protected]:
```

ssh into server as root:

```bash
ssh [email protected]
```

run bash script:

```bash
bash server-setup.sh
```

follow instructions.

## Caddy service

Check status:

```bash
systemctl status caddy
```

Reload config without downtime.

```bash
systemctl reload caddy
```

Docs: https://caddyserver.com/docs/running#using-the-service

## Useful systemd commands

Check status of service.

```bash
systemctl status app.service
```

Restart service manually:

```bash
systemctl restart app.service
```
Loading