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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ jobs:
- 'crates/pjs-core/**'
- 'crates/pjs-domain/**'
- 'crates/pjs-wasm/**'
- 'crates/pjs-demo/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'rust-toolchain.toml'
Expand Down Expand Up @@ -394,12 +395,17 @@ jobs:
~/.cargo/registry
~/.cargo/git

# pjs-demo excluded: 3000+ LOC of demo server binaries with zero tests
# (already listed under codecov.yml's ignore:, so this doesn't change
# what's reported); skipping its LLVM-instrumented compile here saves
# build time. See #318.
- name: Generate workspace coverage
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "true"
run: |
cargo llvm-cov nextest --all-features --all-targets --workspace \
--exclude pjs-demo \
--lcov --output-path lcov.info \
--status-level fail --final-status-level skip

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Both `WebSocketRateLimiter::spawn_cleanup_task` and `InMemoryDictionaryStore::new` require a Tokio runtime to actually spawn their periodic cleanup task; outside one, they log a warning and skip spawning rather than panicking, so bare construction remains usable from synchronous/non-async call sites — and, for `WebSocketRateLimiter`, a later call from within a runtime can still succeed (#346, #329)
- `test_wire_stalled_write_times_out_and_closes_connection` (added alongside #364's write-timeout fix) asserted only that a stalled connection closes within its deadline, with no negative control distinguishing that closure from an unrelated closure path (e.g. rejecting the large inbound frame). Added `test_wire_stalled_write_stays_open_before_write_timeout`, a sibling test reusing the same stall setup but with a `write_timeout` far longer than its observation window. It asserts both halves of the causal claim: the connection stays open through the window, and once it does close, the elapsed time is `>= write_timeout` — proving the stalled-write phase was actually reached and that closure is genuinely gated on the timeout, not just a closure-within-window race (#357)
- `AdaptiveStreamController::start_streaming` discarded the `JoinHandle` of the per-session frame-streaming task, so a panic in that task (e.g. on a malformed frame) was silently swallowed by the runtime and the session simply stopped streaming with no diagnostic signal. The handle's `AbortHandle` is now stored on `WebSocketStreamSession` and aborted on session teardown (`remove_session`, `cleanup_expired_sessions`, and a repeated `start_streaming` call replacing an in-flight task), and a supervisor task awaits the `JoinHandle` and logs via `tracing::error!` when it resolves to a panic. `AxumWebSocketTransport::handle_socket` now tracks which streaming sessions each connection created and calls `remove_session` for all of them on connection teardown, so the abort actually happens on client disconnect in production, not just in tests; a new background sweep (`with_rate_limit_config`, weakly holding the controller so it can't keep it alive past the transport's own lifetime) also periodically removes sessions that outlive any connection (#315)
- `crates/pjs-demo` was unbuildable by any documented method (`-p pjs-demo`, `--manifest-path`, or `cd`-ing into the directory): it was excluded from the Cargo workspace while its manifest used full `{ workspace = true }` field inheritance, which Cargo cannot resolve for a crate outside the workspace — a regression of #112. Chose option (a) from #112: restore `crates/pjs-demo` to `[workspace] members`, mirroring how `crates/pjs-bench` was restored in PR #190, while keeping it out of `default-members` so a bare `cargo build`/`cargo test` still only builds `pjs-core`, `pjs-domain`, and `pjs-wasm`. Fixed one additional compile error and one clippy lint this surfaced: three `crates/pjs-demo/src/data/*.rs` files imported `rand::Rng` instead of `rand::RngExt` (`random_range`/`random_bool` moved trait in `rand` 0.10), and a clippy `unnecessary_sort_by` lint in `websocket_streaming.rs`. Documented commands (`README.md`, `.claude/rules/continuous-improvement.md`) now pass pjson-rs feature flags with the required `pjson-rs/` prefix, since `pjs-demo` declares no `[features]` of its own. CI's `rust` paths-filter now includes `crates/pjs-demo/**` so PRs touching only the demo crate trigger quality/build/test/doctest/docs checks; the coverage job excludes `pjs-demo` (untested demo binaries would dilute the reported percentage) (#318)
- `InMemoryEventPublisher.event_log` evicted at capacity by removing an arbitrary `DashMap`-iteration-order slice, not the oldest entries, and `recent_events()` compounded this by reversing that same arbitrary order and presenting it as "most recent." `StoredEvent` now carries a `sequence: u64` stamped by a monotonic counter at store time (`EventId` is a random UUIDv4 and cannot serve as an ordering key); eviction removes the lowest-`sequence` entries down to 9,000 in a single pass (correctly bounding even oversized `publish_batch` calls, not just 1,000 per call), and `recent_events()` sorts by `sequence` to reliably return the newest entries first. `publish_batch` reserves its sequence block with one `fetch_add` before parallelizing, since stamping per-event inside `rayon`'s `into_par_iter` would assign sequences in thread-scheduling order rather than batch order (#350)
- Removed stale TODO comments in `axum_adapter.rs` describing authentication and HTTP rate limiting as unimplemented; both already exist (`ApiKeyAuthLayer`/`JwtAuthLayer` in `infrastructure::http::auth`, `RateLimitMiddleware` in `infrastructure::http::middleware`) and are now documented and cross-referenced in place (#319)
- `DomainEvent::event_id()` derived a content-hash-based UUID, so two structurally-identical events (same variant, session/stream ID, and timestamp) produced the same `EventId`. `InMemoryEventPublisher.event_log` keys solely on `EventId`, so colliding events silently overwrote each other with no error. `InMemoryEventPublisher` and `HttpEventPublisher` now mint a fresh `EventId::new()` per stored/published event at publish time, guaranteeing distinct identity even for structurally-identical events. `EventPublisherGat`'s doc now states this as the identity contract implementors must follow (#328)
Expand Down
119 changes: 119 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[workspace]
members = ["crates/*"]
default-members = ["crates/pjs-core", "crates/pjs-domain", "crates/pjs-wasm"]
exclude = ["crates/pjs-demo", "crates/pjs-js-client"]
exclude = ["crates/pjs-js-client"]
resolver = "3"

[workspace.package]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,9 @@ cargo nextest run --workspace
# Run benchmarks
cargo bench -p pjs-bench

# Run demo servers
cargo run --manifest-path crates/pjs-demo/Cargo.toml --bin interactive-demo-server --features "simd-auto,schema-validation,compression,http-server,websocket-server"
cargo run --manifest-path crates/pjs-demo/Cargo.toml --bin simple-demo-server --features "simd-auto,http-server"
# Run demo servers (feature names are forwarded to pjson-rs, hence the prefix)
cargo run --manifest-path crates/pjs-demo/Cargo.toml --bin interactive-demo-server --features "pjson-rs/simd-auto,pjson-rs/schema-validation,pjson-rs/compression,pjson-rs/http-server,pjson-rs/websocket-server"
cargo run --manifest-path crates/pjs-demo/Cargo.toml --bin simple-demo-server --features "pjson-rs/simd-auto,pjson-rs/http-server"
```

### Feature Flags
Expand Down
2 changes: 1 addition & 1 deletion crates/pjs-demo/src/data/analytics.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Analytics dashboard dataset generation

use super::DatasetSize;
use rand::Rng;
use rand::RngExt;
use serde_json::{Value, json};

/// Generate analytics dashboard data
Expand Down
2 changes: 1 addition & 1 deletion crates/pjs-demo/src/data/ecommerce.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! E-commerce dataset generation for PJS demonstrations

use super::DatasetSize;
use rand::Rng;
use rand::RngExt;
use serde_json::{Value, json};

const PRODUCT_NAMES: &[&str] = &[
Expand Down
2 changes: 1 addition & 1 deletion crates/pjs-demo/src/data/social.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Social media dataset generation for PJS demonstrations

use super::DatasetSize;
use rand::Rng;
use rand::RngExt;
use serde_json::{Value, json};

const USERNAMES: &[&str] = &[
Expand Down
2 changes: 1 addition & 1 deletion crates/pjs-demo/src/servers/websocket_streaming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ fn create_demo_frames(data: &JsonValue) -> DomainResult<Vec<pjson_rs::stream::St
}

// Sort by priority (highest first)
frames.sort_by(|a, b| b.priority.value().cmp(&a.priority.value()));
frames.sort_by_key(|a| std::cmp::Reverse(a.priority.value()));

Ok(frames)
}
Expand Down
Loading