Skip to content

fix: return the socket from the client emitters and true from the server ones - #189

Merged
electrohyun merged 10 commits into
mainfrom
fix/emitter-return-values
Aug 7, 2026
Merged

fix: return the socket from the client emitters and true from the server ones#189
electrohyun merged 10 commits into
mainfrom
fix/emitter-return-values

Conversation

@electrohyun

Copy link
Copy Markdown
Owner

Summary

contract.ts declared ): void on every emit and every listener method, so smocket returned undefined where socket.io returns something. socket.on('a', f).on('b', g) threw. Found by installing the package and using it from outside, not by the suite, which never read a return value.

Nothing here was guessed. Every position was read off a real socket.io 4.8.3 server first and the declaration written to match. The table is below, and it disagreed with the expectation in two places.

Contract Expression socket.io 4.8.3 returns
ClientSocketContract client.emit(...) the socket
ServerSocketContract serverSocket.emit(...) true, not the socket
ServerContract io.emit(...) true
NamespaceContract nsp.emit(...) true
BroadcastContract io.to(r).emit(...) true
TimeoutEmitterContract client.timeout(ms).emit(...) the socket
SocketTimeoutContract serverSocket.timeout(ms).emit(...) true
TimeoutBroadcastContract io.timeout(ms).to(r).emit(...) true
VolatileServerSocket serverSocket.volatile.emit(...) true
VolatileClientSocket client.volatile.emit(...) the socket
both sockets on once off removeAllListeners onAny offAny onAnyOutgoing offAnyOutgoing the socket
NamespaceContract nsp.on(...) the namespace
ServerContract io.on(...) io.of('/'), a Namespace

Three things came out of that.

The emit shape is not uniform. Only the client returns the socket. Every server-side emit returns true, including the server socket's, which was expected to match the client's and does not.

TimeoutEmitterContract had to split. It was shared, with SocketTimeoutContract extends it, but the client's timed emit returns the socket and the server's returns true. An interface cannot narrow an inherited return type to an unrelated one, so SocketTimeoutContract declares its own two emit forms instead.

ServerContract.on stays void. It is the one position where socket.io disagrees with itself: the declaration says this, the runtime hands back io.of('/'). Narrowing to NamespaceContract fails the Ensure<> proof, since socket.io's declared Server has no name; narrowing to this would copy a promise its own runtime does not keep. Left alone and the reason is in the code.

Related issue

No tracking issue. Depends on #188, which this branch is stacked on. Removes the section C entry #187 adds, which is why that section exists rather than the fix reading as a section A removal.

Checklist

  • Tests added or updated. src/emitter-returns.test.ts is 12 dual-run cases, one per row above plus a chaining case that proves two chained registrations both fire. Both targets are green at 188. One case failed on the mock first and was a bad assertion of mine, not a mock bug: it compared two separate client.timeout() results, which are the same socket on real socket.io and a fresh wrapper on smocket. It now asserts chaining without asserting identity.
  • Behavior verified against real socket.io. Every row was measured before anything was declared, and the six Ensure<> proofs re-judge the narrowed returns, which is what caught the shared timeout interface.

@electrohyun electrohyun self-assigned this Aug 7, 2026
@github-actions github-actions Bot added the bug Something isn't working label Aug 7, 2026
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@electrohyun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c9e3cc12-5e22-4af7-bed0-933c31f0e178

📥 Commits

Reviewing files that changed from the base of the PR and between 3e02ec6 and 47f3394.

📒 Files selected for processing (1)
  • src/connect-url.test.ts
📝 Walkthrough

Walkthrough

Changes

Emitter contracts and server API

Layer / File(s) Summary
Public return contracts and server type
src/contract.ts, src/index.ts
Public emitter methods now return this or boolean according to their runtime behavior. SmocketServer is exported with adapter and nextConnection.
Runtime return values and inert emitters
src/mock-server.ts
Runtime emitters return chainable instances or boolean results. Failed, volatile, and timed emitters retain reusable return objects.
Return-value validation and conformance
src/emitter-returns.test.ts, src/connect-url.test.ts, src/index.test.ts, docs/conformance.md, scripts/conformance-report.mjs, docs/decisions/0019-what-counts-as-a-breaking-change.md
Tests and conformance documents cover emitter returns, listener chaining, failed connections, and the exported server type. Observable return values now count as measured behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: client emitters return the socket and server emitters return true.
Description check ✅ Passed The description directly explains the return-value changes, runtime basis, affected APIs, tests, and compatibility decisions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/decisions/0019-what-counts-as-a-breaking-change.md`:
- Around line 32-38: Reorder the “Raising engines.node, lowering it” row in the
decision table so it appears before “A correction toward measured real behaviour
with no observable change.” Preserve both rows’ existing classifications and
wording; only change their ordering so the Node version rule takes precedence
when both conditions match.

In `@src/connect-url.test.ts`:
- Around line 170-182: Update the connection test around connect() to await the
client’s next-tick connect_error event before leaving the try block and
restoring console.error. Preserve the existing emit assertions, and ensure the
failed connection’s scheduled console.error runs while the spy remains active.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ae3c5e64-257d-4493-afba-c6c3a78a38b5

📥 Commits

Reviewing files that changed from the base of the PR and between 8e6e012 and 3e02ec6.

📒 Files selected for processing (9)
  • docs/conformance.md
  • docs/decisions/0019-what-counts-as-a-breaking-change.md
  • scripts/conformance-report.mjs
  • src/connect-url.test.ts
  • src/contract.ts
  • src/emitter-returns.test.ts
  • src/index.test.ts
  • src/index.ts
  • src/mock-server.ts

Comment thread docs/decisions/0019-what-counts-as-a-breaking-change.md
Comment thread src/connect-url.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant