0030. The public direct connection API settles on close
Status: Accepted · 2026-08-13 · #277, #350 Governed by: 0014, 0020, 0028
TL;DR
Server.connect()andnextConnection()are the two public halves of Smocket's direct connection API. They pair admitted sockets in per-namespace FIFO order. Closing the server discards unclaimed sockets and rejects pending or later observers with an ordinaryError.
Decision
Real Socket.IO opens a client through socket.io-client and exposes the server side through a
connection listener. It has no Server.connect() or nextConnection() counterpart. Smocket
keeps that compatible path through connect(url), while the concrete Server also supports a
direct test API that avoids the origin registry.
Server.connect(namespace?, options?) remains public and joins SmocketServer. It normalizes
the namespace, preserves the event-map direction, accepts the same Smocket ConnectOptions as
connect(url), and uses the Manager grouping fixed by 0028.
nextConnection(namespace?) is its observing half. Both default to /.
Each existing namespace keeps one FIFO queue of admitted but unclaimed server sockets and one
FIFO queue of waiting observers. Either call may arrive first after that namespace exists. The
root exists when the server is constructed. A named static namespace must first be registered
through of() or established by an earlier nextConnection() because connect() does not
register a rejected client destination. Multiple calls pair in connection order, and
normalization never merges distinct namespaces. nextConnection() and a connection listener
observe the same server Socket under the ordering fixed by
0014.
If middleware completes repeatedly, lifecycle listeners observe each completion while the
direct API queues that admitted Socket once; repeated completion does not create another Socket.
A rejected or cancelled admission never consumes an observer. The observer remains queued for the next admitted socket. This is proved with that later admission as the completion marker, not by waiting for a timeout.
Close ends the direct connection API together with the socket lifecycle. A socket already
claimed by nextConnection() stays that call's result and is disconnected normally. An
admitted but unclaimed socket is discarded. A pending observer and every observer created after
close reject with Error('server is closed'). Waiting observers for a dynamic namespace that
has not been created reject the same way. The dynamic parent admission and child creation rules
established by #269 stay unchanged.
Alternatives rejected
- Remove
Server.connect()and hide native connections behind test helpers. The exported class already exposes the method, native adapter tests use it, and the publicnextConnection()observer needs an intentional direct driver. - Leave observers pending after close. A promise that can no longer receive a socket must settle when the lifecycle that owned it ends.
- Resolve with
undefinedor a cancellation sentinel. That would widen every successful call and make an admitted server Socket harder to use. - Return unclaimed sockets after close. Those sockets have already entered teardown and cannot represent a new connection observation.