v2.179.0: Zero Errors, Zero Warnings
node-opcua 2.179.0
Compare: v2.178.0...v2.179.0
A robustness and performance release. The client's keep-alive and transaction handling no longer mistake a slow link for a dead server; the encoding path is measurably cheaper; and a family of lifecycle races in the server and address space was found and fixed after the test suite was moved to a parallel runner. Also a full biome pass across the monorepo (0 errors / 0 warnings) and a new CI gate for fixed test ports.
🚀 Features
BinaryStream
- Optional growable mode: the stream can now extend its own buffer instead of requiring the final size up front, which is what lets the extension-object encoder backpatch rather than pre-size. [3cc5ad7]
🐛 Fixes
Client — connection liveness
A connected client could conclude a healthy server had gone away, or quietly lose a request that was still in flight. These are one theme, fixed together:
- client: keep-alive must not treat a timed-out transaction as contact with the server — a slow answer was being read as a dead one. [d10f2aa]
- client: a
ServiceFaultis genuine server contact; treating it as silence could tear down a working session. [cce2c1a] - client: anchor session lifetime on the last answer actually received. [9cbe6e9]
- client: subscription lifetime must not count unanswered publishes. [1f4fb66]
- client: hold transactions while the connection is down, and always resolve them — previously a caller could wait forever. [fc69569]
- client: stop orphaning a transaction that hits
BadSessionIdInvalid. [114785c] - client: the keep-alive scheduler no longer delays and wedges itself. [910bc68]
- secure-channel: deliver an abandoned request like any other
ServiceFault, so the caller gets an answer rather than nothing. [65d3548]
Encoding / dataTypes
- dynamic-extension-object: pair each DataType with its own description. The 1.03 loader zipped two arrays by index, but abstract types are skipped, so after the first skip the pairing drifted — encodings were attached to the wrong description and several standard classes (
FetchResultDataType,TransferResultErrorDataType,ParameterResultDataType,TransferResultDataDataType) were never registered at all. A client needing one of them failed far from the cause. The assert that would have caught this had been commented out with "in some circumstances like Euromap, this assert fails" — it was right to fail, and is restored. [cc88e24] - dynamic-extension-object: report unregistered dataTypes once, by name, instead of scattered log lines that nobody reads. [2ac9951]
- client-dynamic-extension-object: restore the null-NodeId fallback for enumeration fields. [f1bbad1]
- variant:
Variant.cloneis a true deep copy — the shallow copy leaked shared state between values. [66a4f01] plus the follow-up repair of what that exposed. [9e21a60]
Server
- server:
suspendConnectionnow waits for the listener to be released. It called back whileclose()was still in flight, sorestoreConnectioncould re-listenon a port the old handle still held — anEADDRINUSEout ofresumeEndPoints, hit during certificate rotation. [f3154ec] - server:
createSessionrefuses withBadServerHaltedonce the engine is shutting down. A request already in flight could land after the address space was disposed and surface asCannot read properties of null (reading 'findDataType')from a promise nobody awaited — taking the process down rather than failing one request. [f3154ec] - server: scale a channel's listener ceiling with its session count. [c647f70]
- server: default
writeAttributestatusCode toGoodinstead of rejecting. [00534f6] - server: correct
createSubscriptionDiagnosticsArraybrowseName and options [be070ca], and restore the explicit namespace-0 browseName forSubscriptionDiagnosticsArray. [a459eaf]
Address space
- address-space:
UAVariable.readValueAsyncmust honourisAccessRestricted. [8f13f6a] - address-space: stop asking massively-used types to drop absent references.
_propagate_refnever installs back references forHasTypeDefinition/HasModellingRule— "because there are too many" — but removal did not mirror that, so detaching any node producedCannot find reference to remove. 101 of them in a single CI run. [0f43a1a] - role-set: rebind
Identitiesto the store sobindRoleSetcannot shadow it. [08fb5a9]
⚡ Performance
- secure-channel: encode each message once instead of twice. [31818de]
- data-value:
DataValue.cloneno longer copies its payload twice. [194cf15] - extension-object: backpatch the body length instead of pre-sizing the buffer. [168f87d]
- variant: memoise the built-in codec per numeric DataType [83ade33]; index the typed-array helper table numerically [ac06826].
- data-value, variant: take the cheap constructor branch for
undefined. [d6fb939] - binary-stream:
memcpyinwriteArrayBufferrather than a byte loop. [2255ec3] - server: hold the monitored-item registries in
Maps [21559ce]; stop the notification path re-cloning the sampled value [f16bfea].
🧹 Chore / CI / internal
Most of the fixes above were found by this work, which is the reason it is worth listing.
- ci: the test suite now runs in parallel, one worker per file. This removed the idle-machine slack that had been hiding the lifecycle races fixed above. [ci: parallelise the test jobs]
- test: per-package
pnpm -r run testworks again — it had been broken (the root.mocharc.jsrequiredts-node, which is not installed anywhere), and no CI job read those config files. Repairing it revealed 22 test files (~172 tests) that no runner had ever collected, plus two dependencies missing from a manifest. [217a89d] [177e3b0] [fd5c4ee] [0dd52a7] - test-runner: collect numbered and
repro_specs, not onlytest_— 520 → 542 files. [7088422] - test(mocharc): every package's mocha configuration on one shape, enforced by a new
check-mocharctool. Mocha does not merge configs, so a package wanting one different setting had to restate the loader list; 24 of them carried a relative path that only worked by accident of directory depth. [ccc6e2a] - tools: new
check-test-portsscanner, gating CI — collisions, ports pinned inside the ephemeral range, ports derived asport + 1(bound but written nowhere), and ports belonging to OPC UA itself (4840) or to services a developer is likely running (3000,5432, macOS AirPlay on5000). It found two live collisions nobody had hit yet. [45662a0] [397cbb0] [e6e7e11] - test(transport): several tests asserted event sequences under a comment admitting the order varies by Node version, gated by fixed
setTimeouts. They now assert what is causally guaranteed and wait for the events themselves. [4ecaf82] [da436ed] [8069189] [d4268e2] [cb00093] - chore: full biome pass across the monorepo — 0 errors, 0 warnings — plus a CI lint gate. [c02a076] [95bbc44]
- ci: GitHub Actions moved off the deprecated node20 runtime (checkout v4→v7, setup-node v4→v7, pnpm/action-setup v4→v6, upload-artifact v4→v7). [80c6613] [709042e]
- leak-detector: stop a reported leak from hanging the worker. [8fd5090]
- tools: ignore brackets inside strings when bounding a statement [cdb0f0d]; register
check-debug-login the pnpm lockfile [6abc2d7].
🙏 Contributors
Thanks to everyone who contributed to this release.
The Sterfive Team