All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Added
Changed
Fixed
[0.4.2] - 2026-04-18
Changed
- Credo dependency switched from git branch back to hex —
mix.exspinned credo togithub: "rrrene/credo", branch: "release/1.7"as a workaround for a 1.7.x sigil crash on Elixir 1.20+. The fix has landed incredo 1.7.18on hex, so the dep now reads{:credo, "~> 1.7", only: [:dev, :test], runtime: false}. Dev/test dependency only — no runtime or consumer impact; the shipped hex tarball is byte-identical to 0.4.1 modulo thismix.exsline and the lockfile. - ex_unit_json lockfile bump 0.4.2 → 0.4.3 — Picked up during
mix deps.get. Dev/test dependency only; no runtime impact.
[0.4.1] - 2026-04-18
Changed
- Handler message contract retired two unreachable shapes —
{:frame, term}and{:frame_error, {:decode_error, term}}are removed from@type handler_message, the default handler,USAGE_RULES.md, and theErrorHandlingexample. Both shapes were documented in R047 prep but never actually delivered:{:frame, _}was theother ->catch-all ofroute_data_frame/2, unreachable becauseMessageHandler.handle_control_frame/3consumes every non-text/non-binary decode output before it gets there;{:frame_error, {:decode_error, _}}depended onErrorHandler.handle_error/1returning non-:stopfor{:bad_frame, _}, butcheck_fatal/1classifies every bad frame (and every unclassified error) as fatal, so the recoverable branch never ran. Decision was to retire rather than expand reachability — there is no concrete user need for recoverable frame-decode behavior, andFrame.decode/1only emits one error shape ({:error, "Unknown frame type: ..."}), which is a protocol violation with nothing to recover from.MessageHandler.handle_message/2anddecode_and_handle_control/1simplify to always produce{:protocol_error, _}in the decode-error branch;create_handler/1drops the{:decode_error, _}routing clause.handle_frame_error/2's tightened@specnow declares{:protocol_error, term()}as the only reachable error tag. This is a breaking change against the prepared v0.4.0 handler contract — not against the published v0.3.1 surface, since the retired tuples were added in v0.4.0 doc work (R047) but were never emitted at runtime (R048) - Handler message contract is now self-documenting and typed —
ZenWebsocket.Clientexposes@type handler_message/0and@type handler/0, and thehandlerfield in the GenServer state type useshandler()instead of(term() -> term()). USAGE_RULES.md adds a "Handler Message Reference" section with input-shape and default-handler translation tables, a custom-handler example, and a note on the:protocol_error/:frame_errorpayload asymmetry. The default handler now forwards:unmatched_responseto the parent as:websocket_unmatched_response— previously the_othercatch-all dropped it silently, making orphan JSON-RPC responses invisible to callers relying on default-handler delivery. Integration tests inclient_test.exscover the custom- and default-handler paths. The:frameand:frame_errorhandler shapes were discovered to be currently unreachable (control-frame consumption and fatal bad-frame classification respectively); both call sites carryTODO(Task R048)markers for the retire-or-reach decision (R047) - Testing policy now permits opaque Gun transport message-shape fixtures — The "NO MOCKS ALLOWED" rule in CLAUDE.md and AGENTS.md is amended with a narrow, fenced exception: test doubles may construct the four Gun transport tuples (
:gun_upgrade,:gun_ws,:gun_down,:gun_error) using real pids (fromself()/spawn) and real refs (frommake_ref/0), because Gun'spidandstream_refare opaque BEAM primitives with no public contract — there is no behavior for a fixture to drift against. Business-logic mocking (API response fixtures, auth-flow simulation, exchange-behavior simulation) remains explicitly prohibited;MockWebSockServerand real-API tests remain the source of truth for allClient/ reconnection / subscription / exchange tests. AGENTS.md carries the "what is permitted" pointer; CLAUDE.md holds the full rationale and the "what is NOT newly allowed" list. Unblocks R045 (GunStubtest helper) and R046 (MessageHandlerproperty tests) (R044)
Fixed
- Duplicate live request IDs no longer silently overwrite the first caller —
RequestCorrelator.track/4usedMap.put/3onstate.pending_requests, so tracking a second request whose ID matched an already-pending entry silently replaced the first caller'sfromand timer. The first caller then blocked onGenServer.calluntil its per-callrequest_timeoutfired, and its timeout timer became orphaned against an ID now owned by the second caller.track/4now returns{:ok, new_state}on success and{:error, :duplicate_id, state}on collision, leaving the first caller's pending entry (and its timer) intact. At theClient.handle_call({:send_message, _}, …)call site, the second caller receives{:error, :duplicate_request_id}immediately and no WebSocket frame is sent. No:tracktelemetry is emitted on the duplicate path, keeping event counts honest. Covered by unit tests inrequest_correlator_test.exs(describe "track/4 duplicate ID") and an integration test inclient_test.exs(describe "duplicate request ID (R043)") that fires twosend_messagecalls with the same JSON-RPC id back-to-back and asserts the second returns the error while the first still resolves when the server replies (R043) - Blocked callers no longer hang or inherit stale timeouts on automatic disconnect — On the automatic Gun disconnect/reconnect path,
state.pending_requestswas never drained, so callers blocked onGenServer.callfor a correlated response waited until their per-callrequest_timeoutfired even though the socket was gone.handle_connection_error/2now callsRequestCorrelator.fail_all/2, which replies{:error, :disconnected}to every pending caller and emits[:zen_websocket, :request_correlator, :fail_all]telemetry. Correlation timers now use unique timer refs in their mailbox messages, so a stale timeout from a disconnected or already-resolved request cannot incorrectly time out a new request that reuses the same ID after reconnect (R042) - retry_count not reset after successful reconnect — After a disconnect-reconnect cycle,
retry_countaccumulated instead of resetting to 0. This silently degraded reconnection capability over the lifetime of a long-running process — each successive disconnect cycle had fewer retry attempts available. Now reset to 0 on successful WebSocket upgrade (R030) - Explicit reconnect now preserves the original connection contract —
Client.reconnect/1no longer falls back to URL-only reconnects. Client structs returned byconnect/2orClientSupervisor.start_client/2now retain their validated config plus runtime callbacks such ashandler,heartbeat_config,on_connect, andon_disconnect, so explicit reconnects keep the same headers, timeouts, retry settings, callback behavior, and supervision mode (R030) - Config inspection now redacts header values —
inspect(config)(andinspect(client)output containingclient.config) redacts header values via a customInspectimpl, preventing bearer tokens or API keys from leaking through struct inspection. Debug-mode log line that directly loggedconfig.headersduring WebSocket upgrade has been removed (R030)
Added
GunStubtest helper codifies the R044 transport-shape fence — NewZenWebsocket.Test.Support.GunStubintest/support/exposes four constructors (gun_upgrade/3,gun_ws/3,gun_down/4,gun_error/3) that build the raw Gun transport tuples consumed byMessageHandler.handle_message/2. Every constructor defaults to real pids (self/0) and real refs (make_ref/0) — no fake opaque values, as required by R044. The@moduledocstates the fence verbatim and enumerates what is explicitly not allowed (API fixtures, auth flows, exchange semantics), pointing toMockWebSockServeras the source of truth for behavioral tests. One existing test inmessage_handler_test.exs("handles gun_upgrade message for websocket") adopts the helper to prove it is viable; further adoption is incremental (R045)- MessageHandler property-test coverage — New
message_handler_property_test.exsusesGunStub+stream_datato asserthandle_message/2routing totality and shape determinism: arbitrary non-Gun tuples always route to{:ok, {:unknown_message, _}}without raising;gun_downreturns{:connection_down, pid, reason}for any reason term;gun_errorreturns{:connection_error, pid, ref, reason}for any reason term; text and binary data frames reach the handler callback acrossStreamData.string(:utf8)andStreamData.binary()respectively. Runsasync: trueand is fully pure (no network, no MockWebSockServer), so it stays in the default unit-test band (R046) - Property-based test coverage — Added property tests using
stream_datafor three pure, deterministic modules (R010):Frame: Gun-format and direct-format decode round-trips, constructor round-trips, close-frame normalization (integer code discarded), totality on arbitrary unknown shapesConfig: valid-input totality across all positive-int fields, URL scheme/host validation, per-field non-positive rejection,max_backoff < retry_delayordering constraint,new!/2consistency withnew/2JsonRpc:build_request/2shape invariants, unique ID generation across N calls,match_response/1coverage for result, error, and notification cases- Deferred:
MessageHandlerproperty tests require Gun transport shape fixtures, which are blocked by the current "NO MOCKS" policy — tracked as R044 (policy amendment), R045 (GunStubhelper), R046 (MessageHandler properties)
- Error scenario test coverage — Added explicit coverage for previously-untested error paths (R011):
- Gun error variants in
error_handler_test.exs:{:gun_error, ..., :closed},{:gun_error, ..., :timeout},{:gun_down, ..., :tls_error, []},{:gun_down, ..., :protocol_error, [refs]}, and explain/1 unwrapping for both shapes - Frame corruption in
frame_test.exs: unknown atom frame types, non-tuple input, empty tuple, arity-mismatched:wsframes, unknown inner:wstype, empty and 1MB payloads, deeply nested inner type, map input — all return{:error, _}rather than crashing - Concurrent correlation timeout cleanup in
request_correlator_test.exs: N concurrent tracks timing out clearpending_requestsand emit N telemetry events; timeout on an already-resolved request is a no-op - Rate limit recovery in
rate_limiter_test.exs: queue drain via refill after:queue_full, mixed-cost bucket-capacity cap behavior, refill token cap verification, and concurrent consume calls not losing tokens - Follow-up: The gap R011 surfaced (pending requests not drained on Gun disconnect) is addressed by R042 above.
- Gun error variants in
- Deployment considerations guide — New
docs/guides/deployment_considerations.mdcovering latency sensitivity by strategy type, geographic proximity to exchange matching engines, connection architecture trade-offs (single vs pool vs per-account), production monitoring signals, and cancel-on-disconnect interactions. Framed as "questions to ask yourself" rather than prescriptive rules — the right answer depends on the operator's strategy and constraints. Cross-linked from README guide table and registered inmix.exsExDocGuidesgroup (R025) - Reconnection behavior documentation — USAGE_RULES.md now distinguishes automatic reconnect from explicit
Client.reconnect/1, documenting what is preserved, reset, or carried across each path (R030) - Config preservation regression tests — Mock-server regression tests now run in the default test suite and verify: retry_count resets after successful automatic reconnect, handler callbacks survive reconnect, Config structs remain identical across reconnects, supervised reconnect reruns lifecycle callbacks under
ClientSupervisor, and explicit reconnect preserves the stored connection contract even after the original client is closed (R030)
0.4.0 - 2026-04-12
Added
- Self-describing API via Descripex — All 17 library modules annotated with
use Descripexandapi()macro declarations. RootZenWebsocketmodule usesDescripex.Discoverablefor three-level progressive disclosure:describe/0(library overview),describe/1(module functions),describe/2(full function detail). Existing@docstrings preserved —api()writes machine-readable hints (BEAM slot 5) while@docretains human prose (slot 4). Enables MCP tool discovery and JSON Schema generation. Tests cover all three describe levels and module registration completeness (R040) - Custom client discovery hooks —
send_balanced/2accepts optional:client_discoveryfunction for plugging in custom registries (pg, Horde, :global) instead of local-only discovery.start_client/2accepts:on_connectand:on_disconnectlifecycle callbacks for external registry integration. Default behavior (local discovery vialist_clients/0) unchanged. Documentation with pg and Horde examples in USAGE_RULES.md (R024)
Fixed
- Stale client PIDs no longer crash callers —
send_message/2,get_state/1,get_heartbeat_health/1,get_state_metrics/1, andget_latency_stats/1now checkProcess.alive?beforeGenServer.call. Dead PIDs return appropriate fallbacks (error tuple,:disconnected, ornil) instead of raising:exit.send_balanced/2benefits automatically via existing failover logic. Best-effort guard — callers needing race-proof delivery should usesend_balanced/2with:client_discovery(R024) (R029) - Subscription messages not reaching user handler —
route_data_frame/2sent"method" => "subscription"messages only toSubscriptionManager, never forwarding to the user handler callback. Now updates tracker state and forwards to handler (R038) - Protocol errors not reaching user handler —
handle_frame_error/2stopped the GenServer on protocol errors without notifying the user handler first. Now callshandler.({:protocol_error, reason})before stopping, matching thecreate_handler/1contract (R039) - Double callback delivery bug —
MessageHandler.handle_message/2called user handler, thenroute_data_framecalled it again for every data frame. Addeddecode_and_handle_control/1to MessageHandler for decode + control frame handling without handler invocation; Client GenServer uses this instead. Malformed frames are still classified as fatal protocol errors via ErrorHandler (R035) - Skipped reconnection TODO replaced with real integration test — verifies Client GenServer survives MockWebSockServer disconnect and enters reconnection mode (R033)
- WebSocket upgrade now preserves query parameters from the connection URL — previously
wss://host/path?token=abcwould upgrade as just/path, dropping the query string (R031) DeribitAdapter.subscribe/2,unsubscribe/2,authenticate/1, andsend_request/3now return{:error, :not_connected}when client is nil instead of raisingFunctionClauseError(R027)BatchSubscriptionManagernow handles subscribe failures: marks request as failed with error reason and stops processing instead of silently ignoring the return value (R028)DeribitGenServerAdapter@doccorrected from "handler module" to "handler function" (R028)- ErrorHandling example missing
handle_infoclause —{:websocket_error, reason}messages causedFunctionClauseErrorinexamples/docs/error_handling.ex. Added catch-all error handler clause (R041) subscribe/2return type documented incorrectly — USAGE_RULES.md showed{:ok, subscription_id}but actual spec returns:ok | {:error, term()}(R041)send_message/2examples passed maps instead of binaries — README.md and USAGE_RULES.md examples used%{action: "ping"}but spec requiresbinary(). Fixed to useJason.encode!/1(R041)- Non-existent telemetry event documented —
[:zen_websocket, :client, :message_received]in USAGE_RULES.md replaced with accurate event list (R041) - Stale telemetry events in performance_tuning.md — Events table listed
[:zen_websocket, :request, :start/complete/timeout]and[:zen_websocket, :subscription, :add/remove]which use wrong namespaces. Replaced with actual 16 events from codebase across 6 namespaces (R041) get_state/1misused in performance_tuning.md — Examples showedClient.get_state/1returning full state map. Fixed to useget_latency_stats/1,get_heartbeat_health/1,get_state_metrics/1(R041)- Monitoring return shapes wrong in docs —
get_heartbeat_healthdocumented as%{failures: ..., last_at: ...}but returns%{failure_count: ..., last_heartbeat_at: ...}.get_state_metricsdocumented as%{pending_requests: ..., subscriptions: ..., memory_bytes: ...}but returns%{pending_requests_size: ..., subscriptions_size: ..., state_memory: ...}. Latency stats documented as floats but returns integers. Fixed in USAGE_RULES.md and performance_tuning.md (R041) reconnect/1missing limitation note — Documented without noting it drops custom opts (headers, timeouts, etc.) on reconnect. Added note referencing R030 (R041)- ErrorHandling example
send_message@doc claimed JSON encoding —@docsaid "will be JSON encoded" butClient.send_message/2requires binary. Fixed doc and spec (R041) - Architecture.md claimed "Gun HTTP/2" — Library actually forces HTTP/1.1 ALPN for WSS upgrades. Also claimed "5 functions per module" without qualifying existing modules. Fixed both (R041)
last_heartbeat_atdocumented as DateTime — Docs showed~U[...]but actual value isSystem.monotonic_time(:millisecond)(monotonic integer). Fixed in USAGE_RULES.md and performance_tuning.md (R041)ErrorHandling example understated error surface —
send_message/1doc/spec claimed only:ok | {:error, :not_connected}but delegates toClient.send_message/2which returns:ok | {:ok, map()} | {:error, term()}including{:error, {:not_connected, reason}}variants. Fixed spec and doc (R041)
Improved
Reconnection test now restarts mock server and verifies post-reconnect frame delivery — previously only proved GenServer survived disconnect (R036)
Subscribe test now captures server-received frame and validates JSON-RPC payload structure (method, channels) — previously only checked
:okreturn (R037)Example files updated for handler contract change —
ErrorHandlingexample now handles{:websocket_protocol_error, ...}and{:websocket_frame_error, ...}instead of non-existent{:websocket_error, ...};JsonRpcClient.handle_message/1now accepts pre-decoded maps instead of assuming raw JSON strings (codex review)AGENTS.md module overview corrected — Fixed stale function names across 9 modules (Frame, ErrorHandler, JsonRpc, Reconnection, MessageHandler, HeartbeatManager, SubscriptionManager, RequestCorrelator, RateLimiter). Added missing LatencyStats entry. Fixed test code example using non-existent
Frame.encode/1(codex review)
Changed
- Quality workflow updated — Removed
mix lint,mix typecheck,mix coverage,mix check,mix rebuildaliases from mix.exs. Usemix test.json,mix dialyzer.json --quiet,mix credo --strict --format jsondirectly for AI-friendly structured output.mix securityremains as the Sobelow alias and now includes--skipso.sobelow-skipsis honored for the known low-confidence Recorder findings. - CLAUDE.md imports updated — Added
cli-aliases.mdandagent-economy.mdincludes; reordered to match Elixir Library template - Roadmap reformatted — Migrated to
[D:X/B:Y/U:Z → Eff:W]priority format; archived completed task details to CHANGELOG; added doc-update requirement to all pending tasks - Added
descripex ~> 0.6dependency for self-describing APIs - All docs updated — README, AGENTS.md, CONTRIBUTING.md, USAGE_RULES.md updated to reference JSON output commands instead of removed aliases
- Handler callback contract — valid JSON text frames are now delivered as decoded maps (
%{"key" => "value"}) instead of raw binary strings. Non-JSON text frames remain as binary. If your handler pattern-matches on{:websocket_message, msg} when is_binary(msg)and callsJason.decode/1, update to match on{:websocket_message, %{} = msg}for JSON and{:websocket_message, msg} when is_binary(msg)for non-JSON text (R035) - Root
ZenWebsocketmoduledoc rewritten to document current API — replaces legacy references toConnection,Platform,Behaviors, andDefaultswith actualClient,ClientSupervisor, and module index (R034) - AGENTS.md module overview and file tree updated — Corrected function names/arities for all modules, added missing ConnectionRegistry/Debug entries, updated file organization tree to all 19 modules, removed stale "separate mix project" guidance for examples (R041)
- docs/Architecture.md updated — Added 9 missing modules (LatencyStats, RecorderServer, Testing, ClientSupervisor, PoolRouter, Debug, HeartbeatManager, SubscriptionManager, RequestCorrelator), updated data flow diagram (R041)
- "5 functions" framing updated — USAGE_RULES.md, README.md, and AGENTS.md now describe "5 essential/core functions" with note about additional monitoring and management functions (R041)
- Version bump to 0.4.0
Reverted
- R026 (Deribit example as separate mix project) — ergonomic cost outweighed benefit: broken Tidewave access, broken
.iex.exs, 13+ stale doc references. Examples stay inlib/zen_websocket/examples/
0.3.1 - 2026-01-21
Changed
- Update ex_doc to ~> 0.40 for llms.txt support (AI-friendly documentation)
0.3.0 - 2026-01-21
Added
ZenWebsocket.PoolRoutermodule for health-based connection routing (R022)select_connection/1- select healthiest connection from poolcalculate_health/1- score (0-100) based on pending requests, latency, errors, pressurerecord_error/1/clear_errors/1- error tracking with 60s decaypool_health/1- get health snapshot for all connections- Round-robin fallback when connections have equal health
ClientSupervisor.send_balanced/2for load-balanced message routing (R022)- Routes to healthiest connection using PoolRouter scoring
- Automatic failover on send failure (configurable max_attempts)
- Records errors and emits telemetry on failover
- Telemetry events for pool routing (R022)
[:zen_websocket, :pool, :route]- connection selected with health score[:zen_websocket, :pool, :health]- pool health snapshot[:zen_websocket, :pool, :failover]- failover attempt with reason
AGENTS.mdguide for AI coding agents contributing to the project (R023)- Module overview with key functions
- Project constraints (5 functions, 15 lines, real API testing)
- Testing strategy and common patterns
- Debugging guide (recording, state inspection)
ZenWebsocket.Testingmodule with consumer-facing test utilities (R020)start_mock_server/1- simplified mock server startup with URL generationstop_server/1- cleanup server and resourcessimulate_disconnect/2- trigger disconnect scenarios (:normal,:going_away,{:code, n})inject_message/2- send message from server to connected clientsassert_message_sent/3- verify client sent expected message (string, regex, map, or function matcher)- Helpers integrate with ExUnit setup/on_exit patterns
ZenWebsocket.Recordermodule for session recording (R019)format_entry/3- format frames as JSONL entriesparse_entry/1- parse JSONL entries back to structsreplay/3- stream recorded sessions to handler functionmetadata/1- get session statistics (count, duration, timestamps)
ZenWebsocket.RecorderServerasync GenServer for file I/O (R019)- Buffered writes with periodic flush (1s interval or 100 entries)
- Non-blocking
record/3via send (not call) stats/1returns entries count and bytes written
- Config option
record_to: pathenables session recording (R019)- Records inbound and outbound frames with microsecond timestamps
- JSONL format (one JSON object per line) for streaming writes
- Binary frames encoded as base64
- Close frames include code and reason
Changed
PoolRouter.calculate_health/1usesdiv/2for cleaner integer arithmeticClientSupervisorrestart policy documented in@moduledoc(moved from comment)- Private functions consistently use
@doc falsewith explanatory comments USAGE_RULES.mdexpanded with v0.2.0+ features (R023)- Testing module documentation (replaced MockWebSockServer references)
- Session recording section with Recorder API
- Expanded monitoring/observability section (latency, heartbeat, metrics)
- ErrorHandler.explain/1 usage example
- New config options (record_to, latency_buffer_size)
- Telemetry events reference table
- Updated test count (93 → 395)
Removed
- Stale documentation files superseded by ROADMAP.md and AGENTS.md
docs/TaskList.md,docs/deferred_tasks.mddocs/JsonRPCElixir_MigrationTasks.md,docs/json_rpc_elixir.mddocs/WNX0019_learnings.md,docs/test_roadmap.md
0.2.0 - 2026-01-20
Added
- Building Adapters Guide enhanced at
docs/guides/building_adapters.md(R012)- Adapter decision tree (plain client vs struct vs GenServer)
- Heartbeat interface documentation (
:deribit,:ping_pong,:binance, custom) - Authentication patterns (API key+secret, HMAC signature, OAuth token flow)
- Binance Spot adapter example (non-JSON-RPC pattern)
- Performance Tuning Guide at
docs/guides/performance_tuning.md(R013)- Configuration parameter tuning (timeouts, reconnection, latency buffer)
- Rate limiter tuning with exchange-specific cost functions
- Telemetry events reference table
- Memory characteristics documentation
- Common tuning scenarios (HFT, market data, resource-constrained)
- JsonRpc edge case tests for nil/empty params, empty methods, malformed responses (R016)
ErrorHandler.explain/1returns human-readable error messages with fix suggestions (R018)ZenWebsocket.LatencyStatsmodule for bounded circular buffer latency statistics (R017)Client.get_latency_stats/1returns p50/p99/last/count latency metrics (R017)- Telemetry event
[:zen_websocket, :connection, :upgrade]withconnect_time_msmeasurement (R017) - Telemetry event
[:zen_websocket, :heartbeat, :pong]withrtt_msmeasurement (R017) - Telemetry event
[:zen_websocket, :rate_limiter, :pressure]for backpressure signaling (R021) - Config option
latency_buffer_size(default: 100) for latency stats circular buffer (R017) RateLimiter.status/1now returnspressure_levelandsuggested_delay_msfields (R021)ZenWebsocket.HeartbeatManagermodule for heartbeat lifecycle management (R001)ZenWebsocket.SubscriptionManagermodule for subscription tracking and restoration (R002)ZenWebsocket.RequestCorrelatormodule for JSON-RPC request/response correlation (R003)- Telemetry events for SubscriptionManager:
:add,:remove,:restore(R002) - Telemetry events for RequestCorrelator:
:track,:resolve,:timeout(R003) RateLimiter.shutdown/1for proper ETS table cleanup (R005)- Configurable
max_queue_sizeoption for RateLimiter (default: 100) (R005) - Telemetry events for rate limiter:
:consume,:queue,:queue_full,:refill(R005) - Memory characteristics documentation in RateLimiter @moduledoc (R005)
- Test tagging conventions documentation in test_helper.exs (R015)
- Test coverage exclusion config for non-production modules (T001)
- Test coverage roadmap documentation at
docs/test_roadmap.md - Frame edge case tests for direct frame format decoding (T002)
- Config boundary value tests for
new!/2andrequest_timeoutvalidation (T003) - Reconnection extreme value tests for nil max_backoff and zero retries (T004)
Changed
- RequestCorrelator now stores timestamps and emits
round_trip_msin resolve telemetry (R017) - RateLimiter tracks pressure level and emits threshold-crossing events at 25%/50%/75% queue fill (R021)
- HeartbeatManager emits RTT telemetry on heartbeat responses (R017)
- Client tracks connection timing from connect start to WebSocket upgrade (R017)
- Test coverage metrics now exclude non-production modules (Examples, Test.Support, Mix.Tasks) - reported coverage ~38% → ~70% (T001)
- Frame module now at 100% test coverage (T002)
- Config module now at 100% test coverage (T003)
- Extracted heartbeat logic from Client.ex to HeartbeatManager (789 lines from 870) (R001)
- Extracted subscription tracking from Client.ex to SubscriptionManager (R002)
- Extracted request/response correlation from Client.ex to RequestCorrelator (R003)
- Client.ex now fully delegates to extracted modules; removed dead
restore_subscriptions/4from Reconnection (R004) - Automatic subscription restoration on reconnect via
maybe_restore_subscriptions/1(R002) - Replaced magic numbers with named module attributes in Client, ClientSupervisor, and Reconnection modules (R008)
- Standardized
Debug.log/2to accept onlyConfig.t()struct, removed polymorphic state map interface (R009) - BasicUsage example now uses Deribit testnet instead of echo.websocket.org
- MockWebSockServer handler registration improved in
websocket_init/1 - Downgraded Elixir from 1.20.0-rc.1 to 1.19.5 (fixes Credo false positives)
- RateLimiter
handle_rate_limit/5now accepts config parameter to avoid double ETS lookup (R005)
Fixed
- Dialyzer warning on
Client.maybe_restore_subscriptions/1- expandedClient.state()type to include all fields (R002) - ConnectionRegistry monitor leaks:
cleanup_dead/1andshutdown/0now properly demonitor before deletion (R006) - Flaky tests: migrated from unreliable echo.websocket.org to local MockWebSockServer
- Race conditions in ErrorHandlingTest with proper
wait_for_connection/1polling - MockWebSockServer now raises clear error when TLS certificates unavailable
- Test tagging: 7 integration test files now properly tagged with
@moduletag :integration(R015)mix testnow runs 141 unit tests (~5 seconds vs ~93 seconds for full suite)- Removed incorrect
@moduletag :integrationfromplatform_adapter_template_test.exs(pure unit tests) - Removed module-level tag from
rate_limiting_test.exs(uses@describetagfor integration blocks only)
0.1.4 - 2025-11-05
Changed
- Breaking: DeribitRpc functions now return
{:ok, map()}tuples instead of bare maps for consistency with library conventions - Updated Erlang from 27.3.4 to 28.1.1
- Updated Elixir from 1.18.4 to 1.19.1-otp-28
Improved
- DeribitAdapter and DeribitGenServerAdapter updated to use
withstatements for better error handling - Correlation test improved with MockWebSockServer usage
- Test helper configuration cleaned up for better readability
0.1.3 - 2025-08-11
Fixed
- Compilation error in mix zen_websocket.validate_usage task (regex in module attributes)
0.1.2 - 2025-08-11
Added
- USAGE_RULES.md for AI agents and developer guidance
- Mix task
zen_websocket.usageto export usage rules - Mix task
zen_websocket.validate_usageto validate code patterns - Integration with usage_rules library ecosystem
- JSON export format for usage rules
- Automated code validation for common anti-patterns
Improved
- Documentation with clear usage patterns and examples
- Package metadata for Hex.pm publishing
0.1.1 - 2025-05-24
Added
- Initial release of ZenWebsocket
- Core WebSocket client implementation with Gun transport
- Automatic reconnection with exponential backoff
- Comprehensive error handling and categorization
- JSON-RPC 2.0 protocol support
- Request/response correlation manager
- Configurable token bucket rate limiter
- Integrated heartbeat/keepalive functionality
- Fault-tolerant adapter architecture
- Production-ready Deribit exchange integration
- Connection registry for multi-connection management
- Message handler with routing capabilities
- WebSocket frame encoding/decoding
- Telemetry events for monitoring
- Comprehensive test suite using real APIs (no mocks)
- Full documentation with examples
Features
- Simple 5-function public API
- Financial-grade reliability for trading systems
- Platform-agnostic design with adapter pattern
- Real-world tested against live WebSocket endpoints
- Strict code quality standards (max 5 functions per module, 15 lines per function)