Suggestions to make stompjs more robust and maintainable. References are file:line against the code-quality branch.
-
Break the
Client↔StompHandlercircular dependency.stomp-handler.ts:100holds a back-reference toClientpurely to readheartbeatStrategy(andclient.ts:293reaches into_stompHandler._webSocket). PassheartbeatStrategythroughIStomptHandlerConfigand remove_client— both classes become testable in isolation and you remove a real import cycle. -
Replace the 12
@ts-ignorelines inclient.ts(1015, 1021, 1106, 1141, 1180, 1206, 1229, 1250, 1271, 1300, 1331) with a single private helper_requireHandler(): StompHandlerthat throws a typed error when the handler is null. One chokepoint for "called while disconnected," and you can re-enable@typescript-eslint/ban-ts-commentafterward. -
client.tsis 1334 lines and acts as a god object. Extract three cohesive units: aReconnectScheduler(linear/exponential backoff, jitter,maxReconnectDelay), aLifecycleStateMachine(current state mutations are scattered acrossactivate/deactivate/forceDisconnect/onclose), and a thin passthrough STOMP API. The state machine in particular has a real bug surface (see #6).
-
configure()blindly copies user input (client.ts:738:(Object as any).assign(this, conf)). A typo in user config silently does nothing, and underexactOptionalPropertyTypes, passing{ heartbeatIncoming: undefined }overwrites the default withundefined. Iterate keys explicitly, skipundefined, warn on unknown keys. The same(Object as any).assignanti-pattern repeats atstomp-handler.ts:197, 437, 480, 499, 519, 569, 585andframe-impl.ts:75— all should be{ ...x }. -
Parser swallows handler exceptions to
console.log(parser.ts:227-232). This hides bugs in useronMessage/onConnectcallbacks and bypassesdebug. Route through the debug callback orconsole.errorat minimum. -
Reconnect race in
deactivate(). Lifecycle state is mutated fromactivate,deactivate, the WSonclosehandler, and the connection watcher independently. Ifactivate()is called duringDEACTIVATINGand thendeactivate()again, intermediate intent can be lost. A singletransition(state, intent, event)method removes the surface. -
beforeConnectis not cancellable (client.ts:794). A slow token fetch will continue and create aStompHandlereven afterdeactivate(). Carry anAbortSignalor add an_inFlightConnectguard. -
Connection watcher leak.
_connectionWatcheris only cleared on successfulCONNECTED; ifdeactivate()runs in between, the timer fires later on a closed socket. Clear it indeactivate()andonWebSocketClosetoo. -
dispose()swallows transmit failures (stomp-handler.ts:451). If the DISCONNECT frame fails to send, the receipt watcher registered just above never fires and_closeWebsocketis only reached via WS close. Fall through to_closeWebsocket()/_cleanUp()in the catch.
-
Eliminate
anyon timer handles._ponger: any(stomp-handler.ts:96) and similar elsewhere should beReturnType<typeof setInterval>(ornumber | NodeJS.Timeout). -
closeEventCallbackType<T = any>/wsErrorCallbackType<T = any>(types.ts:40, 48) — defaulting toanydefeats the generic. Define minimalIStompCloseEvent { code; reason; wasClean }andIStompErrorEvent {}and use them directly. -
webSocketFactory?: () => anyinstomp-config.ts:37but() => IStompSocketonClient. Use the strict type everywhere. -
Rename
IStomptHandlerConfig(types.ts:186) — typo "Stompt", and renameheartbeatGracePeriods→heartbeatToleranceMultiplierso the config field matches the handler field it's assigned to (stomp-handler.ts:122). -
Use definite-assignment
!instead of// @ts-ignoreinparser.ts:67, 74.
-
_checkConnection()throwsTypeError(client.ts:1112). Wrong category — it's an invalid-state error. DefineStompNotConnectedError extends Errorfor callers to discriminate. -
No backpressure signal on
publish. SurfaceWebSocket.bufferedAmountor document the lack of flow control prominently. Today an unhealthy network can balloon memory silently. -
Drop deprecated public surface for v8.
src/compatibility/is re-exported fromindex.ts:14-15;publishParamslowercase alias (types.ts:83);Client.over()/Client.client()static factories. Saves ~400 lines, removes the largest cluster of legitimateanyusage, and is the right window to do it before publishing 7.4.0.
-
ESLint config disables real-bug-catchers (
eslint.config.mjs:14-17). Turn back on:@typescript-eslint/ban-ts-comment— would have caught all 12@ts-ignorelines.@typescript-eslint/no-floating-promises— needs typed linting; high value for an async-heavy library.@typescript-eslint/no-explicit-any— at least aswarn.
Also add
tseslint.configs.recommendedTypeChecked(orstrictTypeChecked) andeslint-plugin-importwithimport/no-cycle— the latter would have flagged the Client↔StompHandler cycle. -
Tighten
tsconfig. Turn ondeclarationMap(commented at line 11) for consumer IDE navigation, plusnoPropertyAccessFromIndexSignature,useUnknownInCatchVariables,verbatimModuleSyntax,forceConsistentCasingInFileNames. -
Enforce coverage thresholds.
c8block inpackage.json:70reports but doesn't gate. Addc8 check-coverage --lines 90 --functions 90 --branches 80to CI. -
Tests only run
--project=node(package.json:9). The Playwright config defines chromium/firefox/webkit projects that only run atprepublishOnly— i.e. publish-time is the first signal of browser regressions. Either run them in CI or delete them. -
Audit CI workflows in
.github/workflows/—linux.yml,osx.yml,node-js.ymllikely duplicate work. Consolidate into a matrix.
-
Modernize JSDoc links. All
client.tsdoc-comments use[Foo#bar]{@link Foo#bar}. TypeDoc handles{@link Client.bar}natively, with cleaner output. -
Document
noUncheckedIndexedAccessimpact onIFrame.headers. Consumers readingframe.headers['x-foo']now getstring | undefined. This is the kind of subtle migration note that belongs in the changelog and README, not in code comments.
Highest leverage / lowest risk first:
- #2 (replace
@ts-ignorewith_requireHandler) — small diff, eliminates a category of latent bugs, unblocks #18. - #18 (re-enable ESLint bug-catchers) — surfaces real issues the type system can't catch (floating promises, etc.).
- #1 (break Client↔StompHandler cycle) — small refactor, removes the only legitimate reason for the back-reference.
- #4 (rewrite
configure()+ replace remaining(Object as any).assignpatterns) — fixes a realexactOptionalPropertyTypesbug, removes ~10anywarnings. - #14, #10, #11, #12, #13 (type-safety cleanups) — incremental.
- #5, #6, #7, #8, #9 (concurrency/lifecycle) — needs careful test coverage; consider #3 (extract
LifecycleStateMachine) at the same time. - #17 (v8 cleanup) — schedule for the v8 cut.