Skip to main content

Conformance report

TL;DR Delivery cases are measured against real socket.io and smocket from one test file. The list is generated only when both test projects succeed. Native smocket cases and repository tooling checks are listed separately without a Socket.IO claim.

info

The delivery and native API sections record cases that run against both targets. Repository tooling checks run once in the mock project. This report is not a percentage of Socket.IO's complete API and does not make a 100% compatibility claim.

The dual run​

A test file here never imports a server directly. It calls setupServer() from src/setup-server.ts, which resolves to a real socket.io server or to smocket depending on SMOCKET_TARGET, and the two targets are the two vitest projects pnpm test:real and pnpm test:mock run. The test body is the same either way.

The real target is the oracle, and the order matters. A case is written against it first, so what the case asserts is socket.io's behaviour and not the mock's. Running the identical file against smocket then asks one question, whether the same assertion still holds. That is why a red mock target is read as a divergence located rather than a test that needs adjusting.

No case asserts non-receipt by waiting. A timeout only says an event had not arrived yet, and it buys that weak claim with a slow suite. The cases send a later event to the same socket instead and assert the ordering: once the marker has arrived, anything that was supposed to precede it would already be there. The helpers are in src/test-events.ts.

Both targets run on every push and pull request, across three operating systems, so the comparison is continuous rather than something last confirmed by hand. The single CI badge on the README goes red if either target does.

Verified against real socket.io​

Every case below ran against socket.io 4.8.4 and socket.io-client 4.8.4 first and against smocket second, from the same test file, and passed on both. Each links to the test that pins it.

Connection and identity​

Pairing a client with its server socket, the id both sides see, and the first emit.

Rooms​

Join and leave, and which members an emit to a room reaches.

Broadcast​

The broadcast variants and the sockets each one targets or excludes.

Broadcast chaining​

Narrowing a broadcast further, and whether the order of the narrowings matters.

Local broadcast socket lookup​

Fetching existing local server Sockets through canonical room, exclusion, sender, and namespace selection.

Local bulk broadcast membership​

Joining and leaving rooms synchronously through canonical room, exclusion, sender, and namespace selection.

Local bulk broadcast disconnect​

Disconnecting selected namespace Sockets or their shared Manager groups through canonical management selection.

Aliases and compression modifiers​

send, write, open, close, socket in, and compression chaining. Compression packet effects stay outside the transport-free mock boundary.

Namespaces​

What a namespace isolates: connections, emits, rooms, and socket ids.

Dynamic namespace parents​

Parent admission, concrete child lifecycle, setup snapshots, and direct broadcasts. Narrowed operator construction is covered, while narrowed delivery remains unverified under 0029.

Acknowledgements​

The trailing callback and emitWithAck, in both directions.

Acknowledgement lifecycle​

Connection-owned teardown guards, broadcast partial responses, and direction-specific one-shot consumption when response encoding fails.

Payload serialization​

JSON results, snapshot timing, invalid data, and reference isolation.

Acknowledgement timeouts​

timeout(ms) on a single emit, and what a late ack does.

Broadcast acknowledgements​

Collecting an ack from every recipient of a broadcast, and answering on expiry.

Broadcast Promise acknowledgements​

Awaiting every selected recipient, including timeout errors, snapshots, and wrapper composition.

Connection middleware​

io.use: admitting a connection, rejecting one, and the order two run in.

Server Socket packet middleware​

Per-packet ordering, mutation, acknowledgements, rejection, and independent asynchronous continuation.

Handshake​

The handshake fields a mock can source, and how auth and query reach them.

socket.data​

The per-socket store, its isolation, and its lifetime.

Volatile emits​

What volatile delivers in steady state, and the one window where it drops.

Catch-all listeners​

onAny / offAny on both sides, and the events they do not see.

Outgoing catch-all listeners​

onAnyOutgoing / offAnyOutgoing, and where in the send path they fire.

Reserved event names​

Which public emit names throw before delivery or outgoing observation.

Listener removal​

off and removeAllListeners, including the places the two sides disagree.

Listener introspection​

Side-specific snapshots, live arrays, counts, names, and once wrappers.

Disconnect​

Room cleanup, the reason each side reports, and what happens to a pending ack.

Queued delivery lifecycle​

Dropping or draining already-queued packets according to the receiver and teardown path that owns the connection lifecycle.

Shared Manager disconnect​

Namespace grouping, connection-wide teardown order, independent Managers, and reconnect cleanup.

Server close​

Server-wide teardown, its reasons, and what happens to pending acknowledgements.

Return values​

What emit, listener, middleware, connect, and disconnect methods hand back, and which chain.

Inherited emitter compatibility​

Node and component-emitter aliases, listener order, wrappers, removal, delegation, and max-listener state.

smocket only​

These have no oracle to compare against: they cover the API smocket adds (differences.md §B) and the internals behind it, so they run the same under both targets. They are listed apart because nothing about socket.io follows from them.

Native acknowledgement teardown race​

Discarding or retaining acknowledgements as appropriate when a smocket outgoing observer tears down a connection mid-send.

connect(url) and the origin registry​

Resolving a url to a server, and what the url contributes to the handshake.

Binary passthrough guard​

Keeping out-of-scope binary-containing packets on the existing in-memory path without an encoding claim.

Adapter API​

Registering an adapter that changes the routing decision.

Adapter lifecycle​

Factory isolation, setup boundaries, and whole-socket cleanup.

TracingAdapter​

Recording immutable final broadcast routing decisions without payloads.

Deterministic broadcast dropping​

A smocket-only final-recipient filter by sid, including acknowledgements, cleanup, namespace isolation, and adapter composition.

Broadcast management adapter boundary​

Keeping local management selection on canonical Socket state instead of custom event routing and delivery filtering.

DelayingAdapter​

Holding a socket's client-inbound stream so a race can be interleaved on purpose.

Native broadcast Promise policy​

Applying smocket-only pre-connect volatile selection before acknowledgement counting.

Socket id encoding​

The encoder behind the id shape the dual run pins.

Public entry points​

What the package exports, including the io name the substitution path needs.

Public direct connection API​

Pairing direct clients with server sockets, namespace queue order, admission outcomes, and close settlement.

SharedWorker host bridge​

Validating port messages, generations, acknowledgements, ordering, errors, and explicit teardown around the existing in-process server.

SharedWorker client facade​

Connecting through the narrow page API, listener behavior, acknowledgements, stale-generation suppression, and bridge errors.

SharedWorker lobby application handlers​

Running the documented lobby handlers against real Socket.IO and smocket, including duplicate-label identity, readiness, start, and disconnect.

Repository tooling​

These checks run once in the mock project. They verify repository tooling, not Socket.IO delivery or the native smocket API.

Conformance report failure handling​

Rejecting unsuccessful test processes and aggregate reports before generating or certifying the case list.

How to add a case​

The gaps above are the shortest route into this repository, because a contribution here is judged mechanically rather than by taste.

  1. Put it in the area file it belongs to under src/. A new file also needs an entry in the area table in scripts/conformance-report.mjs, which fails the run rather than dropping an unlisted file from this page.
  2. Run pnpm test:real first. Red here means the case states something socket.io does not do, so the case is wrong and the mock is not involved yet.
  3. Then run pnpm test:mock. Green on the real target and red on the mock is a divergence found, not a mistake made, and it arrives with its reproduction already written. Open it as an issue or fix the mock to match.
  4. Prove non-receipt with a marker, never with a timeout. See the dual run above.
  5. Run pnpm conformance and commit the regenerated page. CI runs the same generation and fails if this file no longer matches the suite.

Supported versions​

Each row is answered by a CI job rather than by a claim, so the evidence is in .github/workflows/ci.yml.

QuestionAnswerJob
Which Node runs the suite22 and 24 on Linux, current LTS on Windows and macOStest
Which Node runs the published package20 and up, the floor engines.node declaresdeclared node floor
Which TypeScript consumes the types5.0.2 and up, under NodeNext and Bundlerpackage
Which Socket.IO the cases hold forExact root server and client pins, recorded abovetest, public surface ledger
Which browser the mock runs inChromium, mock target onlybrowser

Each release follows one declared Socket.IO server and client target under 0043. The dual run compares that target's behavior with smocket, and type checks compare the covered declarations. The root pins and installed versions must agree; examples and consumers must use the same exact upstream pins. A differing version is unverified unless explicitly validated. Earlier releases retain their documented support, without a maintenance guarantee.

The browser row is narrower on purpose. A page cannot host a socket.io server, so there is no real target to compare against there, and the job asks only whether the mock behaves in a browser the way it behaves in Node.

What a version number promises​

The number promises fidelity to socket.io, not the result your suite got last week. A correction that moves the mock toward measured real behaviour is therefore a minor release even when it turns a passing test red, because the diverging result was never what the version promised.

  • A correction toward measured real behaviour: minor when it changes what is delivered, patch when nothing observable moves.
  • Newly covered socket.io surface: minor.
  • Removing or altering a deliberate divergence from differences.md §A: major. Adding one: no bump, since it documents what was already happening.
  • A public type change: minor if existing call sites still compile, major otherwise.
  • Raising engines.node: major. Lowering it: minor.

Before 1.0.0 every rule applies one place to the right, the way npm reads a 0.x range. A release that changes what is delivered also carries its own section in the notes, with the before and after as results and a link to the case above that pins the new behaviour. The reasoning is in 0019.