Repository navigation
Releases: CLDMV/stubborn-tcp
Release list
v1.0.4
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 ofsrc/(function names kept), anddist/index.cjs, the ~1 kB CommonJS wrapper. The root-levelindex.mjs,index.cjsandstubborn-tcp.mjsfiles are gone.exportsonly ever exposed the package root, so this matters only to tools that bypassexportsand load files by path;mainandmodulenow point intodist/. Stack traces point into the minified bundle. - Smaller tarball.
filesnow ships onlydist/(without source maps),types/, the README and the license.test/andexamples/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.mtsdeclarations are now published (types/, wired throughtypesand thetypesexport condition). TypeScript projects that previously treated the package as untyped will now type-check against these declarations. - Development export condition. A
stubborn-tcp-devexport condition resolves to the unbuiltsrc/files; it only takes effect when a consumer opts into that condition. publishConfig.accessis nowpublic(v1.0.3 declaredrestricted).
🔧 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 on127.0.0.1with no network access, and characterization tests cover the rest of the API, including TLS over a pre-shared key. The CommonJS entry is tested withnode:testagainst the builtdist/. - Added the fleet ESLint and Prettier configuration, a check-only pre-commit hook, and
@cldmv/fix-headerswith refreshed file headers.
🔧 Dependencies
- Runtime: none, unchanged.
engines.nodestays>=14.0.0for consumers; the bundle is built withtarget: "node14". - Development dependencies added for the new toolchain:
tsup,typescript,vitest,@vitest/coverage-v8,@cldmv/vitest-runner,eslint,prettierand their plugins,@cldmv/configsand@cldmv/fix-headers(#1). @cldmv/fix-headersships at^2.2.0, bumped from^2.1.4(#16); it was first added at^2.1.3and moved to^2.1.4in #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 asnode_modules. Version 2.2.0 makes the@Last modified byheader tag follow content edits only, so a header-only rewrite keeps the recorded editor instead of replacing it.@cldmv/configsships at^1.2.4, bumped from^1.2.2(#16). It provides the sharedfix-headersconfiguration that.configs/fix-headers.jsonextends, and 1.2.4 turns offforceAuthorUpdateandforceLastModifiedAuthorUpdate(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.jsonand the lockfile; no file headers were restamped. - Development Node.js floor. The test and build toolchain needs Node.js 22.12 or newer (
vitest5 supports^22.12.0 || ^24.0.0 || >=26.0.0, and@cldmv/fix-headers2.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. UsesetIdleTimeout(ms)(or theidleTimeoutoption) to time out idle connections.- An
errorevent with no listener no longer throws. If you relied on the crash, attach anerrorlistener;failToConnectnow 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...