19 Commits
Author SHA1 Message Date
Markk116 c84d3b4bcf add license 2026-05-26 23:31:41 +02:00
Markk116 4d05eebe00 doc: create readme 2026-05-26 23:29:29 +02:00
Markk116andGitHub 9bff0c6fdf Merge pull request #4 from Markk116/claude/refactor-connection-tasks-fF25i
Add streaming brotli compression and hot-reloadable config (livevue.toml)
2026-03-14 12:10:31 +01:00
Claude 1035fd179f Refactor fanout to per-connection tasks with debouncing and reconnection grace
- Add RenderTrigger enum to connection.rs (avoids circular imports)
- Replace ConnectionState.sse_sender with trigger_tx + pipe_tx channels
- Add get_trigger_sender and get_pipe_sender to ConnectionManager
- Extract render_and_send helper to eliminate duplicated render/SSE logic
- Implement connection_task: long-lived per-connection coroutine that owns
  the SSE pipe, debounces render triggers (2ms), and survives disconnects for
  a 30s grace period to allow clean reconnection
- Update sse_handler: spawns connection_task on fresh connect, hands new SSE
  pipe to existing task on reconnect; removes cleanup-on-disconnect spawn
- Update spawn_fanout: now a pure router — sends RenderTrigger::Invalidated
  to each subscribed connection's task; no render logic remains in fanout
- Bump broadcast channel capacity from 256 to 16384 in Store::new
- Merge brotli compression branch (config hot-reload, BrotliBody middleware)

https://claude.ai/code/session_01QiTaFtwXETxJDk79XcgfrN
2026-03-14 11:09:49 +00:00
Claude 6d91fd9c36 Add streaming brotli compression and hot-reloadable config (livevue.toml)
Key changes:

- **src/config.rs** – `LiveVueConfig` (server host/port, brotli quality +
  window size), loaded from `livevue.toml`. A notify-based file watcher
  reloads the config on any file change; a SIGHUP handler does the same on
  demand. Falls back to compiled-in defaults when the file is absent.

- **src/brotli_layer.rs** – `BrotliBody`, a custom `http_body::Body` wrapper
  that streams brotli-compressed data through a single persistent
  `brotli::CompressorWriter`. The encoder survives across SSE event flushes,
  so its sliding window accumulates context from all prior events —
  progressively better compression as the stream grows. Both quality (0–11)
  and window size (lgwin 10–24, i.e. 1 KB – 16 MB) are taken from config.
  `brotli_compression` is an axum `from_fn_with_state` middleware that
  activates only when the client sends `Accept-Encoding: br`.

- **src/server.rs** – `AppState` gains a `config: SharedConfig` field.
  `AppState::new` takes the config; `AppState::new_default` is a zero-config
  convenience constructor.

- **livevue.toml** – documented example config with inline comments
  explaining each field and the brotli window-size tradeoff table.

- **examples/todo** – loads config via `load_and_watch_config`, derives the
  bind address from `config.server`, and applies `brotli_compression` as a
  router layer.

https://claude.ai/code/session_01UnQQkkwts64FPUsSzFfdQb
2026-03-12 07:06:10 +00:00
Markk116andGitHub a09ac32bef Merge pull request #3 from Markk116/claude/fix-todo-query-lOZlM
Add mutation_exec! macro and PgQuery trait for database operations
2026-03-12 07:46:23 +01:00
Claude 9dcb0d6dc8 Refactor Query contract: uniform ergonomics for SQLite + PostgreSQL
- Add `publish_key()` default method to `Query` trait (returns `None`)
- Add `mutation_exec!` macro for SQLite INSERT/UPDATE/DELETE ops; overrides
  `publish_key()` so the mutation carries its own invalidation key
- Add `Store::exec()` which runs a mutation and auto-publishes its key —
  action handlers no longer need a separate `store.publish()` call
- Add `PgQuery` trait + `pg_query_one!` / `pg_query_all!` macros
  (feature-gated on `pg_replication`) mirroring the SQLite Query API
- Add `cx.run_pg(pool, query)` to `RenderContext` which executes a PgQuery
  and auto-subscribes its cache key — no more manual `cx.subscribe()` calls
- Update todo example: replace plain mutation fns with `mutation_exec!` structs
  and `store.exec()` calls; remove `action_then_publish` helper
- Update pg_replication example: add `ListMessages` via `pg_query_all!` and
  replace manual subscribe + raw query with `cx.run_pg()`

https://claude.ai/code/session_01HXe8rAZPweU9j2piBo9MdG
2026-03-11 22:37:33 +00:00
Markk116 23fab7a5ff make overview of pending todo's 2026-03-10 23:05:49 +01:00
Markk116 ae9064ee76 fix up pg replication example 2026-03-10 22:47:17 +01:00
Markk116andGitHub a1e2effe96 Merge pull request #2 from Markk116/claude/pg-replication-module-ATq5z
feat: add pg_replication module for reactive WAL-based cache invalidation
2026-03-09 12:08:20 +01:00
Claude b57dbd0fa0 feat: add pg_replication module for reactive WAL-based cache invalidation
Implements a PostgreSQL logical replication module that streams WAL
changes, buffers them per XID for transaction atomicity, and publishes
CacheKey invalidation events to the existing framework broadcast channel.

Key design choices:
- Transaction atomicity: changes are buffered until COMMIT so the fanout
  always receives a consistent view. A 1000-row bulk-insert emits one
  table-level CacheKey, not 1000 row events.
- Native protocol: uses a self-contained raw TCP + postgres-protocol
  implementation for the replication connection (tokio-postgres 0.7 does
  not expose copy_both_simple publicly).
- Corrected pgoutput v1 parser: original maybe_sql_integration code
  incorrectly read XID from DML messages; DML messages carry no XID in
  proto v1 — only Begin does.
- Clean integration: publishes CacheKey::Channel events to store.events
  so the existing fanout task picks them up with zero changes to the
  fanout loop.
- New cx.subscribe(key) API on RenderContext for manually registering
  subscription keys (needed when queries go through PgPool, not cx.run).

New files:
  src/pg_replication/mod.rs       — PgReplicationListener, key helpers
  src/pg_replication/wal_parser.rs — pgoutput v1 binary protocol parser
  src/pg_replication/emitter.rs   — WalEmitter (tx-buffering + CacheKey emit)
  src/pg_replication/proto.rs     — raw TCP PG wire-protocol connection
  examples/pg_replication/main.rs — live message board example
  docker-compose.yml              — PG 16 container with wal_level=logical

Run the example:
  docker compose up -d
  cargo run --example pg_replication --features pg_replication

https://claude.ai/code/session_01SLGgXeqKV2o7KTmCaQZZPg
2026-03-08 22:12:01 +00:00
Markk116 2358ca29c7 dump some extra files 2026-03-08 22:34:03 +01:00
Markk116andGitHub ed85af4a4f Merge pull request #1 from Markk116/claude/fix-sse-pipe-events-y7Nxd
Fix SSE pipe: subscriptions never registered, malformed events, fanout skipping
2026-03-08 21:08:37 +01:00
Claude 0ab0f79749 Fix SSE pipe: subscriptions never registered, malformed events, fanout skipping
Three bugs preventing any SSE events from reaching clients:

1. SubscriptionRegistry::clone() deep-cloned the DashMaps instead of sharing
   them. The sse_handler spawned a task with a disconnected clone, so
   subscriptions.update() wrote into a throwaway copy — the fanout always saw
   an empty registry and found zero connections to push to. Fixed by wrapping
   both DashMaps in Arc so clone() is a cheap pointer copy sharing live state.

2. The SSE event data payload used .join("\ndata: ") to pre-embed the `data:`
   prefix into the string, then handed it to axum's Event::data() which does
   its own \n-splitting to add `data:` prefixes. This produced double-prefixed
   lines (`data: data: elements ...`) that DataStar couldn't parse. Fixed by
   using .join("\n") and letting axum format the data lines correctly.

3. The fanout skipped connections with None signals (new connections that had
   not yet received a client action). This broke clock-style server-push events
   on fresh connections. Fixed by falling back to "{}" instead of continue-ing.

https://claude.ai/code/session_0168cLRd1wr6LK9FjBsjA37W
2026-03-08 19:24:22 +00:00
Markk116 b10326b242 bodged refactor 2026-03-08 20:10:15 +01:00
Markk116 71e63e4410 incremental progess 2026-03-08 17:22:13 +01:00
Claude 6605bf452f Fix DataStar v1 RC8 signal attribute incompatibilities
Two bugs that caused signals to not work on the frontend:

1. data-on-signal-change → data-on-signal-patch
   The data-on-signal-change attribute was removed in RC8.
   The replacement is data-on-signal-patch.

2. data-bind-newTodo="" → data-bind="newTodo"
   HTML normalises attribute names to lowercase at parse time, so
   data-bind-newTodo becomes data-bind-newtodo in the DOM. DataStar
   would then bind to signal $newtodo instead of $newTodo, breaking
   the two-way binding. The value syntax data-bind="newTodo" preserves
   the camelCase name correctly.

https://claude.ai/code/session_01JThiQbp3Bn9J1VhBpRuz4u
2026-03-06 20:13:55 +00:00
Claude 8535d39012 Refactor: untangle framework library from todo example
The codebase is now split into two clear layers:

Framework library (src/):
- lib.rs — public API surface, re-exports all framework types
- signal.rs — SignalOpts, Signal, SignalStore, mangle/global/client_only helpers
- query.rs — Query trait, CacheKey, query_one!/query_all! macros
  (macro paths updated from $crate::framework::* to $crate::*)
- context.rs — RenderContext, ComponentTree, ComponentNode
- connection.rs — ConnectionManager, SubscriptionRegistry, ConnectionState
- sse.rs — format_patch_elements, format_patch_signals
- server.rs — AppState, SharedState, sse_handler (pure framework plumbing)

Todo example (examples/todo/):
- main.rs — SQLite setup, seeding, server bootstrap
- app.rs — router, document_shell, resolve_and_render, render_page,
  action_then_render, and all HTTP handlers
- queries.rs — Todo model, ListTodos/GetTodo queries, mutation helpers
- todo_list.rs — TodoList page component
- about.rs — About page component

Run the example independently with: cargo run --example todo

https://claude.ai/code/session_01JThiQbp3Bn9J1VhBpRuz4u
2026-03-06 16:51:53 +00:00
Markk116 2569596ca5 First Commit, it compiles! 2026-03-05 12:38:22 +01:00