Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
75 changes: 75 additions & 0 deletions .github/workflows/barebuild.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: barebuild

# BareBuild is a self-contained subproject that only shares this repo for convenience.
# Its CI runs ONLY when barebuild/ changes, and BareDOM's ci.yml ignores barebuild/ —
# so the two projects' pipelines never trigger each other.
on:
push:
branches: [main]
paths:
- 'barebuild/**'
- '.github/workflows/barebuild.yml'
pull_request:
branches: [main]
paths:
- 'barebuild/**'
- '.github/workflows/barebuild.yml'

concurrency:
group: barebuild-${{ github.ref }}
cancel-in-progress: true

jobs:
barebuild:
name: Lint, Test & Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'

- uses: actions/setup-node@v6
with:
node-version: '22'

- uses: DeLaGuardo/setup-clojure@13
with:
cli: 'latest'

- name: Setup clj-kondo
run: |
curl -sLO https://raw.githubusercontent.com/clj-kondo/clj-kondo/master/script/install-clj-kondo
chmod +x install-clj-kondo
sudo ./install-clj-kondo --dir /usr/local/bin --version 2026.01.19
clj-kondo --version

- name: Setup Babashka
run: |
curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
chmod +x install
sudo ./install --dir /usr/local/bin
bb --version

- name: Install deps
working-directory: barebuild
run: npm ci

- name: Lint (product + demo)
working-directory: barebuild
run: clj-kondo --lint src test read_demo/src read_demo/test

- name: Unit tests (Node)
working-directory: barebuild
run: npm test

- name: Server handler tests
working-directory: barebuild
run: npm run test:server

- name: Release build (Closure Advanced)
working-directory: barebuild
run: npm run build
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
name: ci

on:
# BareBuild (barebuild/) is a self-contained subproject with its own workflow
# (barebuild.yml). Ignore it here so BareDOM CI never runs on barebuild-only changes.
pull_request:
branches: [main]
paths-ignore:
- 'barebuild/**'
push:
branches: [main]
paths-ignore:
- 'barebuild/**'

concurrency:
group: ci-${{ github.ref }}
Expand Down
4 changes: 4 additions & 0 deletions barebuild/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/dist
/out
/.shadow-cljs
/node_modules
19 changes: 19 additions & 0 deletions barebuild/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Changelog

All notable changes to BareBuild are documented here. This project adheres to
[Semantic Versioning](https://semver.org/).

## 0.1.0

First release. BareBuild — a read-only server-resource read projection runtime for
BareDOM — and its showcase demo are implemented.

- **Runtime (product).** The `<server-resource>` element and the pure `step` lifecycle
(fetch, sort, page, rejection, keep-stale, contract validation, echo-adoption,
trailing-fetch, network/protocol failures, disconnect-abort, SSR boot), plus the shared
`consumer-resource/register!` mechanism for authoring consumers.
- **Demo (showcase).** A Babashka tasks server and a live page where one `<server-resource>`
drives four example consumers — stat, progress, table, and search-field — from server
state.

Read-only; writes (commands, mutations) are a later phase.
21 changes: 21 additions & 0 deletions barebuild/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Alexander van Elsas

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.
128 changes: 128 additions & 0 deletions barebuild/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# BareBuild

**A tool that supports presenting data in a Web Component based UI using only server state [BareDOM](https://github.com/avanelsas/baredom).**

BareBuild attempts to support presentational web components using server state only. A client
does not need business logic, store or runtime framework. The two main parts of BareBuild are:

- **`<server-resource>`**: a non-visual custom element that holds one immutable resource
value, coordinates network delivery, and projects user intent into the URL.
- **`consumer-resource/register!`**: The mechanism that is used to author *consumers*. consumers
are thin elements that translate/project an accepted server value onto a specific web component.

The idea behind BareBuild is that you write a pure projection and a render function. BareBuild owns the whole lifecycle.
A server holds all state and is the only source of truth.

## The loop

Everything is one server-driven cycle:

1. **Get Intent**: A gesture (or the page URL on load) says what the user wants to see.
2. **Fetch from the server**: `<server-resource>` asks the server for exactly that.
3. **The server holds state**: The server answers and its accepted value is the only source of truth.
4. **Render the server state**: The consumer projects that value into a web component.
5. **Repeat**: The next gesture becomes new intent, and the loop turns.

The URL always mirrors the current intent, so every view is a shareable link and the back
button works. State is a succession of immutable values, no atoms, no signals, no
mutable store.

## How it works

At the top level, `<server-resource>` sits between the server and web components.
Plug consumers in with `register!`:

```mermaid
%%{init: {'themeVariables': {'fontSize': '18px'}}}%%
flowchart TB
SERVER["Server<br/>(plain JSON envelope)"]
subgraph SR["&lt;server-resource&gt;"]
direction TB
WIRE["wire · JSON → CLJS"]
STEP["step · pure lifecycle"]
EXEC["executor · decisionless edge"]
end
URL["browser URL + history"]
CONS["Consumers<br/>(consumer-resource/register!)"]
COMP["BareDOM components"]

SERVER -->|"envelope"| WIRE
WIRE --> STEP
STEP --> EXEC
EXEC -->|"fetch"| SERVER
EXEC -->|"url-write"| URL
URL -->|"intent (connect / back-forward)"| STEP
EXEC -->|"notify"| CONS
CONS -->|"render accepted value"| COMP
COMP -->|"gesture"| CONS
CONS -->|"submit-intent!"| STEP
```

The runtime itself is one pure loop. Events go in, a next value plus effects comes out, and an
executor that performs them:

```mermaid
%%{init: {'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
EVENTS["Events in (closed set)<br/><br/>:connected (embed)<br/>:intent-patch (query-patch, gesture-class)<br/>:url-changed (query)<br/>:response<br/>:protocol-failed<br/>:network-failed<br/>:disconnected"]

STEP["step (pure)<br/>resource × event → resource′ + effects<br/><br/>resource value:<br/>:url-intent · :last-accepted · :last-failure<br/>:active-request (request/id, query)<br/>:request-count · :history-policy · :endpoint"]

EFFECTS["Effects out (data)<br/><br/>:fetch (endpoint, query, request/id)<br/>:url-write (params, mode)<br/>:notify-consumers (resource)<br/>:abort (request/id)<br/>:diagnostic (stale-*)"]

EXEC["executor (decisionless edge)<br/><br/>fetch · history push/replace<br/>applyResource · AbortController"]

EVENTS --> STEP --> EFFECTS --> EXEC
EXEC -->|"response · failure · gesture · popstate"| EVENTS
```

- **Pure**: Every decision lives in `step` and is visible in the returned effects.
The executor only performs them (fetch, history, notify, abort). `step` is testable
and replayable from an event log.
- **One request in flight**: `start-request` mints a monotonic `:request/id`; `pending?`
and `installable?` derive purely from the value. A response is installed only if its id
matches the live request. A gesture made mid-flight is picked up by a single trailing
fetch once the in-flight request clears.
- **Two conversions only**: JSON↔CLJS at the network edge, CLJS→DOM at the component edge.
CLJS values in between.

> **Integrating a server?** The endpoint must return a specific JSON envelope — see the
> [server contract](./docs/server-contract.md). For the full data flow with a worked consumer
> example, see [`docs/architecture-diagram.md`](./docs/architecture-diagram.md); to write a
> consumer, see [`docs/authoring-a-consumer.md`](./docs/authoring-a-consumer.md).

## Status

**Pre-release, read-only (v1).** Not yet published to npm (`"private": true`). Writes: commands, mutations, optimistic updates — are a separate, later phase.

## Layout

| Path | What |
|---|---|
| `src/barebuild/` | **the product** — the pure core (`resource`, `wire`, `utils`), the `register!` mechanism (`consumer_resource`), and the `<server-resource>` element |
| `read_demo/` | **the demo** — example consumers, a Babashka dev-server, and a live page (showcase; never shipped) |
| `docs/` | [`server-contract.md`](./docs/server-contract.md), [`architecture-diagram.md`](./docs/architecture-diagram.md), [`authoring-a-consumer.md`](./docs/authoring-a-consumer.md) |
| `test/barebuild/` | product unit tests |

## Develop

```sh
# from this directory:
npm run compile # compile the ESM lib
npm run build # release build (Closure Advanced)
npm test # run the unit tests under Node
```

Lint: `clj-kondo --lint src test`.

BareBuild imports a few BareDOM utilities directly from `../src` (one shared `du`, no fork)
and bundles them into its dist, so the product is self-contained and has no runtime
dependency on BareDOM. (An app whose consumers drive BareDOM components installs
`@vanelsas/baredom` itself — that's the app's dependency, not BareBuild's.)

Running the showcase demo (a live page driving BareDOM components from a tasks server) has
its own guide, see [`read_demo/README.md`](./read_demo/README.md).

## License

[MIT](./LICENSE) — same as BareDOM.
8 changes: 8 additions & 0 deletions barebuild/bb.edn
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{:paths ["read_demo/dev-server"]
:tasks
{server {:doc "Run the BareBuild dev-server (the contract oracle + demo backend)."
:requires ([server])
:task (server/-main)}
test:server {:doc "Run the dev-server handler tests."
:requires ([server-test])
:task (server-test/run)}}}
Loading