Implement WebSocket enhancements with backoff and validation features#16
Conversation
…etection - Add BackoffStrategy, WebSocketReconnectHandler, WebSocketMaxRetriesHandler, WebSocketValidationErrorHandler types - Extend WebSocketOptions: backoffStrategy, maxReconnectDelayMs, jitter, heartbeatTimeoutMs, schemas - Add computeDelay() for linear/exponential/custom backoff with full jitter - Upgrade attemptReconnect() to fire onReconnect/onMaxRetriesReached handlers - Add notifyValidationError() + schema lookup in handleMessage() (US2) - Add pong timeout timer in startHeartbeat()/stopHeartbeat()/handleMessage() (US3) - Add onReconnect(), onMaxRetriesReached(), onValidationError() public methods - 30 tests (15 existing + 15 new) — 654 total passing, 0 failures - Coverage: statements 95.48%, functions 100%, lines 95.48%, branches 89.41% - Add websocket-advanced-example.ts with 5 usage examples - Update bytekit.wiki/WebSocketHelper.md and docs/guides/REALTIME.md Closes T001–T033 in specs/005-websocket-advanced/tasks.md
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
📦 Bundle Size ReportTotal dist size: 1.2M View detailed breakdown |
|
📊 Code Coverage ReportCoverage: 87.38% ✅ Great coverage! |
| // Example 1: Exponential backoff with onReconnect / onMaxRetriesReached | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example1_smartReconnection() { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, an unused function should either be removed (if it is truly dead code) or have its usage made explicit (by calling or exporting it). Since this is an example function in an example file, removing it would defeat its purpose. The best non‑breaking fix is to mark it as an exported function so that it is clearly part of the public API of this example module, making it “used” from the perspective of tools that analyze across files.
Concretely, in examples/websocket-advanced-example.ts, change the declaration on line 7 from async function example1_smartReconnection() to export async function example1_smartReconnection(). No new imports or other definitions are required. This preserves all existing behavior inside the function and simply allows other files or tooling to import and run the example, which resolves the “unused function” finding.
| @@ -4,7 +4,7 @@ | ||
| // Example 1: Exponential backoff with onReconnect / onMaxRetriesReached | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example1_smartReconnection() { | ||
| export async function example1_smartReconnection() { | ||
| const ws = new WebSocketHelper("wss://api.example.com/stream", { | ||
| reconnect: true, | ||
| maxReconnectAttempts: 8, |
| }); | ||
|
|
||
| // Observe the reconnect lifecycle — no string-matching on error messages needed | ||
| const unsubReconnect = ws.onReconnect((attempt, delay) => { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, unused variables should be removed or, if they represent necessary handles, actually used. Here, unsubReconnect is only referenced in commented-out code, so the best minimal fix is to stop assigning the result of ws.onReconnect(...) to a variable and instead either (a) call it without capturing the return value, or (b) also comment out the declaration line. To preserve the example’s instructional value and existing functionality, we should keep the onReconnect registration (since it has side effects) but drop the unused variable binding.
Concretely, in examples/websocket-advanced-example.ts, within example1_smartReconnection, replace the line that declares const unsubReconnect = ws.onReconnect(...) with a bare call ws.onReconnect(...). This keeps the behavior identical (the reconnect handler is still registered) while removing the unused variable. No imports, methods, or new definitions are required.
| @@ -17,7 +17,7 @@ | ||
| }); | ||
|
|
||
| // Observe the reconnect lifecycle — no string-matching on error messages needed | ||
| const unsubReconnect = ws.onReconnect((attempt, delay) => { | ||
| ws.onReconnect((attempt, delay) => { | ||
| console.log(`⟳ Reconnecting… attempt ${attempt} in ${delay}ms`); | ||
| }); | ||
|
|
| console.log(`⟳ Reconnecting… attempt ${attempt} in ${delay}ms`); | ||
| }); | ||
|
|
||
| const unsubMaxRetries = ws.onMaxRetriesReached(() => { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, unused variables should either be removed or their associated value should be used. Here, unsubMaxRetries and unsubReconnect are subscription handles that are meant to be called during cleanup but their calls are commented out, making both variables unused. The cleanest fix that preserves intended functionality is to turn the commented-out cleanup lines into real code, so the unsubscribe callbacks are actually invoked and the variables are read.
Concretely, within examples/websocket-advanced-example.ts, in example1_smartReconnection, change the “Cleanup” section at the bottom from comments into executable statements. This will read and invoke unsubReconnect and unsubMaxRetries, satisfying the rule and aligning behavior with the comment’s intention. No new imports or types are required, and no existing imports need to change.
| @@ -38,9 +38,9 @@ | ||
| ws.send("subscribe", { channel: "prices" }); | ||
|
|
||
| // Cleanup | ||
| // ws.close(); | ||
| // unsubReconnect(); | ||
| // unsubMaxRetries(); | ||
| ws.close(); | ||
| unsubReconnect(); | ||
| unsubMaxRetries(); | ||
| } | ||
|
|
||
| // ───────────────────────────────────────────────────────────────────────────── |
| // Example 2: Custom backoff function (Fibonacci-style) | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example2_customBackoff() { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, to fix an “unused function” warning you either remove the function (if it is truly dead code) or make its usage explicit (by calling or exporting it) if it is meant to be part of the API or examples. Removing it entirely is risky in an examples file because it likely serves documentation value even if not currently referenced.
The best fix here, without changing existing behavior, is to mark example2_customBackoff as an exported function so that it becomes part of the module’s public API, similar to how example helpers are often exposed. This change does not alter any current behavior (no existing code paths are modified) but makes the function a legitimate, potentially used symbol from the module’s perspective, addressing the “unused function” warning. Concretely, in examples/websocket-advanced-example.ts, change the declaration on line 50 from async function example2_customBackoff() to export async function example2_customBackoff(). No additional imports or helpers are required.
| @@ -47,7 +47,7 @@ | ||
| // Example 2: Custom backoff function (Fibonacci-style) | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example2_customBackoff() { | ||
| export async function example2_customBackoff() { | ||
| const fibonacci = [1000, 1000, 2000, 3000, 5000, 8000, 13000]; | ||
|
|
||
| const ws = new WebSocketHelper("wss://api.example.com/stream", { |
| // from "bytekit/core" — both produce a SchemaAdapter that can be used here. | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example3_schemaValidation() { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, to fix an unused-function warning you either remove the function entirely or ensure it is used (called or exported). Since this appears to be a worked example that should remain available, the best non‑disruptive fix is to convert example3_schemaValidation into an immediately invoked async function expression (an IIFE). That way, no named function is left unused, but the example logic (schema definition, WebSocket setup, and connect() call) still executes.
Concretely, in examples/websocket-advanced-example.ts, lines 74–120 should be refactored so that:
- The
async function example3_schemaValidation() { ... }declaration is replaced by anasyncarrow function wrapped in parentheses and immediately invoked:
(async function example3_schemaValidation() { ... })();
or, more idiomatically for examples:
(async () => { ... })(); - All the inner code (the
strictSchemafunction,Tradetype,tradeSchema,wssetup, event handlers, andawait ws.connect();) remains unchanged inside that IIFE. - No new imports or external dependencies are needed; we are only changing the way the function is defined and invoked within the same file.
This removes the unused top-level function symbol while preserving the behavior of the example.
| @@ -71,7 +71,7 @@ | ||
| // from "bytekit/core" — both produce a SchemaAdapter that can be used here. | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example3_schemaValidation() { | ||
| (async () => { | ||
| // Minimal mock SchemaAdapter (no external dependencies) | ||
| function strictSchema<T>(validator: (data: unknown) => data is T) { | ||
| return { | ||
| @@ -117,7 +117,7 @@ | ||
| }); | ||
|
|
||
| await ws.connect(); | ||
| } | ||
| })(); | ||
|
|
||
| // ───────────────────────────────────────────────────────────────────────────── | ||
| // Example 4: Pong detection — detect silent connection drops |
| // Example 4: Pong detection — detect silent connection drops | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example4_heartbeatPongDetection() { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, to fix an unused-function warning in an examples file without removing useful sample code, the best options are either (a) reference the function in a way that makes its purpose clear (e.g., export it so it can be invoked from elsewhere), or (b) explicitly call it from some example runner. Removing the function would satisfy the linter but lose a documented example, which is usually undesirable in an examples file.
The minimal, non‑behavior‑changing fix here is to export the example4_heartbeatPongDetection function (and, ideally, the other example functions too, but the warning is about this one). Turning it into an exported function makes it a public API of the examples module, which counts as “used” from the module’s perspective and allows external callers or tooling to invoke this specific example while preserving existing behavior. Concretely, in examples/websocket-advanced-example.ts, change the declaration on line 126 from async function example4_heartbeatPongDetection() to export async function example4_heartbeatPongDetection(). No new imports or additional definitions are required.
| @@ -123,7 +123,7 @@ | ||
| // Example 4: Pong detection — detect silent connection drops | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example4_heartbeatPongDetection() { | ||
| export async function example4_heartbeatPongDetection() { | ||
| const ws = new WebSocketHelper("wss://api.example.com/stream", { | ||
| heartbeatIntervalMs: 15_000, // ping every 15 s | ||
| heartbeatTimeoutMs: 5_000, // reconnect if no message within 5 s of ping |
| // Example 5: All features combined | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example5_allFeatures() { |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
To fix an “unused function” finding without changing behavior, you either (1) remove the function if it’s truly dead, (2) ensure it is actually called, or (3) expose it via an export so it becomes part of the module’s API but is not auto-invoked. Here, removing example5_allFeatures would destroy a documented example, and auto-calling it would change behavior. The best fix is to export it, matching the file’s role as an examples module.
Concretely, in examples/websocket-advanced-example.ts, change the definition of example5_allFeatures to be an exported function: export async function example5_allFeatures() { ... }. This is a minimal, one-line change that keeps all existing logic identical, doesn’t introduce side effects, and gives the function a clear use: other code (or tests) can import and run this example explicitly. No additional imports or definitions are required.
| @@ -148,7 +148,7 @@ | ||
| // Example 5: All features combined | ||
| // ───────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| async function example5_allFeatures() { | ||
| export async function example5_allFeatures() { | ||
| // Simple structural validator (replace with zodAdapter/valibotAdapter in production) | ||
| const priceSchema = { | ||
| parse(data: unknown) { |
| }); | ||
| const log: string[] = []; | ||
| const unsub1 = wsh.onReconnect(() => log.push("h1")); | ||
| const unsub2 = wsh.onReconnect(() => log.push("h2")); |
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 months ago
In general, to fix an unused variable in tests you either remove the variable if it’s genuinely unnecessary, or use it in an assertion that matches the intended test behavior. Here, the test title explicitly mentions that “each unsubscribes independently,” but only unsub1 is ever invoked, so the best fix is to use unsub2 as well and extend the test to actually verify its independent unsubscription.
Concretely, in tests/websocket-helper.test.ts, within the "US1 [T015] multiple onReconnect subscribers — all notified; each unsubscribes independently" test, we should:
- After the first reconnect where both handlers fire and
unsub1()is called, perform a second reconnect to confirm onlyh2fires (this already happens). - Then call
unsub2()and trigger another reconnect to confirm no handlers fire anymore, showing thatunsub2works and eliminating the unused-variable warning. This usesunsub2in a way that matches the described behavior without altering existing semantics, only strengthening the test.
No new imports or helper functions are needed; we just add a unsub2(); call and an additional reconnect plus assertion using the existing log array and timing pattern.
| @@ -513,6 +513,13 @@ | ||
| wsh.ws.close(); | ||
| assert.deepEqual(log, ["h1", "h2", "h2"]); // only h2 fires | ||
|
|
||
| unsub2(); | ||
| await new Promise((r) => setTimeout(r, 50)); | ||
|
|
||
| // @ts-expect-error - Test type override | ||
| wsh.ws.close(); | ||
| assert.deepEqual(log, ["h1", "h2", "h2"]); // no further handlers fire | ||
|
|
||
| wsh.close(); | ||
| }); | ||
|
|
📊 Code Coverage ReportCoverage: % ❌ Low coverage - please add more tests |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |


Description
Related Issue
Fixes #(issue number)
Type of Change
Changes Made
Testing
Coverage
Screenshots
Checklist
Breaking Changes
Additional Notes