Skip to content

Releases: CLDMV/stubborn-tcp

Release list

v1.0.4

Choose a tag to compare

@cldmv-bot cldmv-bot released this 05 Oct 04:04
3161620

StubbornTCP v1.0.4 Changelog

Release Date: October 2026
Release Type: Patch
Branch: release/1.0.4


Overview

This release makes TLS connections work for the first time, fixes the CommonJS entry for bundlers, and moves the package to a built dist/ layout with TypeScript declarations. It also fixes four long-standing connection bugs: a refused connection could crash the process, failToConnect never fired, calling open() again broke the client, and connectionTimeout dropped idle connections. The published files are different, so read the packaging notes below if you reach into the package or bundle it, and the upgrade notes for the two behaviour changes.

The repository also moves to the CLDMV v4 release flow with a vitest test suite, linting, formatting and file headers (#1).


🐛 Bug Fixes

TLS connections work (#1)

Inside the constructor, the destructured tls option (a boolean) shadowed the imported tls module, so every TLS connection attempt called true.connect(...) and threw TypeError: tls.connect is not a function — from open(), or from the constructor when host and port were supplied. The module is now imported under a different name. Code passing tls: true now connects over TLS instead of throwing; secureConnect, keylog, OCSPResponse and the TLS helper methods work as documented.

require() works in bundles and fails clearly on older Node.js (#1)

The CommonJS entry loaded the ES module through createRequire(__filename), which esbuild and webpack cannot follow. It is now a small hand-written wrapper that uses a plain require("./index.mjs"). The CommonJS entry has always depended on Node.js's synchronous require(esm); on versions without it, require() now throws an ERR_REQUIRE_ESM error that names the supported versions (^20.19.0 or >=22.12.0) and points to import(), instead of a bare loader error. require() still returns the constructor, with StubbornTCP and default aliases.

Unobserved socket errors no longer crash the process (#9, fixes #3)

Every socket error was re-emitted as an error event, and Node.js throws when error is emitted with no listener. A consumer that only listened for connect, disconnect and data, as the README's examples do, crashed on the first refused connection or reconnect attempt, which defeated the point of a client that keeps retrying. error is now emitted only while a listener is attached; otherwise the error goes to the debug event, the onError callback still runs, and reconnects continue.

failToConnect fires (#9, fixes #4)

failToConnect was documented but never emitted, so a consumer waiting on the first connection never learned it had failed. It now fires as (error, handle, instance), once for each attempt (the first one and each reconnect) that fails before connect, and before error. A timeout before connecting reports an ETIMEDOUT error.

Calling open() on a connected client keeps it usable (#10, fixes #5)

open() went through close(), which turned auto-reconnect off, and the replaced socket's late close event then reset the connection state after the new socket had connected. The client ended up connected but unable to write, with a spurious disconnect and reconnects disabled. A replaced socket's events are now ignored, and open() cancels pending reconnect and heartbeat timers without changing the auto-reconnect and heartbeat settings.

open() without arguments uses the constructor's host and port (#11, fixes #8)

The constructor's host and port were never stored, so open() with no arguments threw ERR_MISSING_ARGS. open() now uses its arguments first, then the last host and port used, which start as the constructor's values. With no port from either, it throws a TypeError with code ERR_MISSING_ARGS before touching the current connection.

connectionTimeout only covers connecting; new idleTimeout (#13, fixes #12)

connectionTimeout was set with socket.setTimeout() and never cleared, and Node.js's socket timeout fires on any idle period. An open connection idle for connectionTimeout (10 seconds by default) was timed out, closed and reconnected over and over; consumers with longer heartbeats, such as @cldmv/mxnet-control, were churned. connectionTimeout is now armed when an attempt starts and cleared once connected (after the TLS handshake for TLS). An idle timeout for established connections is available as a new opt-in idleTimeout option (default 0, off) and setIdleTimeout(ms) method.


📦 Packaging

The import surface is the same — the package root, with a default export plus StubbornTCP — but the published files changed:

  • Built output. The code now ships as dist/index.mjs, a single minified bundle of src/ (function names kept), and dist/index.cjs, the ~1 kB CommonJS wrapper. The root-level index.mjs, index.cjs and stubborn-tcp.mjs files are gone. exports only ever exposed the package root, so this matters only to tools that bypass exports and load files by path; main and module now point into dist/. Stack traces point into the minified bundle.
  • Smaller tarball. files now ships only dist/ (without source maps), types/, the README and the license. test/ and examples/ are no longer published; the usage examples remain in the repository. The unpacked size drops from about 80 kB across 10 files to about 57 kB across 8 files (the packed tarball from 21.2 kB to 16.8 kB), even with the new declarations included.
  • TypeScript declarations. Generated .d.mts declarations are now published (types/, wired through types and the types export condition). TypeScript projects that previously treated the package as untyped will now type-check against these declarations.
  • Development export condition. A stubborn-tcp-dev export condition resolves to the unbuilt src/ files; it only takes effect when a consumer opts into that condition.
  • publishConfig.access is now public (v1.0.3 declared restricted).

🔧 CI & tooling

  • Added the full CLDMV v4 workflow set (release flow, CI, CodeQL, Scorecard, dependency review, bundle size, coverage badge) and Dependabot configuration.
  • Tests moved to @cldmv/vitest-runner (tests/*.test.vitest.mjs): the old integration and timeout scripts were ported to run against local servers on 127.0.0.1 with no network access, and characterization tests cover the rest of the API, including TLS over a pre-shared key. The CommonJS entry is tested with node:test against the built dist/.
  • Added the fleet ESLint and Prettier configuration, a check-only pre-commit hook, and @cldmv/fix-headers with refreshed file headers.

🔧 Dependencies

  • Runtime: none, unchanged. engines.node stays >=14.0.0 for consumers; the bundle is built with target: "node14".
  • Development dependencies added for the new toolchain: tsup, typescript, vitest, @vitest/coverage-v8, @cldmv/vitest-runner, eslint, prettier and their plugins, @cldmv/configs and @cldmv/fix-headers (#1).
  • @cldmv/fix-headers ships at ^2.2.0, bumped from ^2.1.4 (#16); it was first added at ^2.1.3 and moved to ^2.1.4 in #14. Version 2.1.4 no longer writes a JavaScript comment into JSON or Markdown files, processes every repeated --input, and never walks dependency folders such as node_modules. Version 2.2.0 makes the @Last modified by header tag follow content edits only, so a header-only rewrite keeps the recorded editor instead of replacing it.
  • @cldmv/configs ships at ^1.2.4, bumped from ^1.2.2 (#16). It provides the shared fix-headers configuration that .configs/fix-headers.json extends, and 1.2.4 turns off forceAuthorUpdate and forceLastModifiedAuthorUpdate (both were on in 1.2.2), so the shared configuration no longer overwrites the recorded author or last editor.
  • #14 and #16 change only package.json and the lockfile; no file headers were restamped.
  • Development Node.js floor. The test and build toolchain needs Node.js 22.12 or newer (vitest 5 supports ^22.12.0 || ^24.0.0 || >=26.0.0, and @cldmv/fix-headers 2.2.0 requires >=22.12.0), and the CI matrix starts at 22.12.0. This affects contributors only; the published package is not affected.

Upgrade notes

  • No API removals — drop-in for v1.0.3 when you import the package root, except for two behaviour changes:
    • setTimeout(ms) after connecting no longer sets an idle timeout. It only changes the connect-phase timeout. Use setIdleTimeout(ms) (or the idleTimeout option) to time out idle connections.
    • An error event with no listener no longer throws. If you relied on the crash, attach an error listener; failToConnect now reports failed attempts too.
  • If you deep-import or bundle files by path (stubborn-tcp.mjs, index.cjs, test/, examples/), switch to the package r...
Read more