Experimental wire protocol
The current NYXDB raw TCP and WebSocket envelope, operations, result statuses, v1/v2 negotiation, streaming termination, and binary block ingest.
NYXDB uses the same request/response envelope over two transports:
- raw TCP carries frames directly on the byte stream;
- WebSocket carries each complete frame verbatim in an RFC 6455 binary message on the same server port.
Wire v1, wire v2, WebSocket transport, and block ingest are experimental.
They are executable and tested, but no long-term compatibility guarantee is
declared. Prefer the repository clients; custom clients must pin a server
revision and maintain protocol conformance tests. The native listener has no
TLS or public Origin-enforcement contract. Put untrusted WebSocket access
behind a reviewed edge and start NYXDB with --require-auth; otherwise a
client can skip auth and remain an implicit full-SQL operator.
Authoritative references: transport envelope, wire v2 ADR, capability handshake, and block codec.
Transport detection
The server listens for raw TCP and WebSocket connections on one IPv4 endpoint. An HTTP Upgrade handshake selects WebSocket; otherwise the connection is parsed as raw frames. WebSocket clients must use binary messages. Text messages are not the SQL protocol.
The native server defaults to 127.0.0.1:7510; the container defaults to
0.0.0.0:7777. Configure clients explicitly because those defaults differ.
Outer frame
All multi-byte structural lengths use network byte order (big-endian).
Request := op:u8 request_id:16 payload_len:u32 payload[payload_len]
Response := request_id:16 status:u8 payload_len:u32 payload[payload_len]The response repeats the 16-byte request ID. Treat it as opaque correlation
bytes, not as the engine's numeric query_id. Generate a fresh nonzero value
for every request that may be outstanding, and reject a response that cannot be
correlated to an in-flight operation. The only documented exception is the
all-zero request ID used by an unsolicited end-user auth revoked control
frame. Raw TCP readers must keep reading until the full declared frame is
available; one recv is not one frame.
Non-block operations are capped at an 8 MiB request payload. Binary ingest has separate limits described below. The server enforces finite connection handshake, idle, partial-frame, and response-write deadlines.
Operations
| Operation | Value | Request payload | Response behavior |
|---|---|---|---|
| Query | 0x02 | SQL UTF-8 | One v1 TSV or negotiated v2 result. |
| Stream | 0x03 | STREAM SELECT SQL UTF-8 | Initial snapshot, then deltas until termination. |
| Query + metadata | 0x04 | SQL UTF-8 | #meta + TSV, or a negotiated schema-2 typed metadata envelope. |
| Stream + metadata | 0x05 | Streaming SQL UTF-8 | Stream plus timing frames. |
| Parameterized query | 0x06 | sql (NUL parameter)* | Prepared execution with metadata and plan-cache status. |
| Parameterized stream | 0x07 | sql (NUL parameter)* | Bound values apply to the subscription lifetime. |
| Ping | 0x08 | Empty | Immediate pong response; bypasses query execution and telemetry. |
| Auth | 0x09 | End user: provider NUL token; operator: NUL system NUL user NUL password | Authenticates this connection; success returns authenticated\n. |
| Cancel request | 0x0A | Exactly 16 raw bytes containing a live one-shot request ID | Requests cooperative cancellation and returns a two-column TSV acknowledgement. |
| Endpoint execute | 0x0B | endpoint_or_view (NUL parameter)* | Executes a published one-shot endpoint or an authorized invocable logical view. |
| Endpoint subscribe | 0x0C | endpoint_or_view (NUL parameter)* | Subscribes to a published streaming endpoint or an authorized invocable logical view; snapshot/delta bodies follow the negotiated stream mode. |
| Ingest block | 0x0D | Binary columnar block | ingested\t<rows>\n on success. |
| Ingest compressed block | 0x0E | Expanded length + LZ4 block | Same acknowledgement after bounded decode. |
| Capability | 0x0F | Version/capability hello | Chooses the connection's wire version and result mode. |
Unknown operations return an error status. Parameter text is positional: the
first NUL-delimited value binds $1, the second $2, and so on. The v1
parameter form cannot represent SQL NULL as a distinct wire value.
Authentication and application endpoint operations
OP_AUTH (0x09) is connection-scoped. Its application-user form is:
provider_name "\0" tokenAn empty provider name is accepted only when exactly one provider is
registered. A lookup or streaming provider converts the opaque token into
server-trusted claims. Clients send only the endpoint's declared positional
parameters; the server appends declared $auth.* values from that claims row.
A client cannot override a claim with an extra parameter.
The operator form is deliberately distinct:
"\0system\0" user "\0" passwordWhen --require-auth is enabled, an unauthenticated connection may negotiate
capabilities at most once, ping, and use either operator or end-user OP_AUTH;
other operations are gated. Negotiate before auth when v2 is required. Without
the flag, the default remains fully open. End-user auth does not close that
operator path.
Authenticate once on a fresh connection. A failed OP_AUTH does not clear or
replace a principal already attached to that socket. Close the transport and
open a new one for every token, provider, or operator-user change.
After successful end-user auth, the connection retains the one-time
connection-setup capability exception and is otherwise restricted to ping,
auth, endpoint execute, endpoint subscribe, and request-ID cancellation for a
one-shot. Raw SQL and block ingest return operation not permitted. Endpoint
invocation sends:
endpoint_or_view ( "\0" parameter )*Endpoint execute/subscribe request parameters use the v1 NUL-delimited text
contract even when the connection negotiated wire v2. A one-shot
OP_ENDPOINT_EXEC response is plain TSV with no metadata envelope. A successful
OP_ENDPOINT_SUB may instead carry negotiated v2 typed snapshot/delta frames
(and optional LZ4), but it does not emit metadata/query-ID status frames because
the endpoint subscription uses the non-metadata stream operation. Decode by
operation, status, and negotiated mode—not connection state alone.
OP_ENDPOINT_EXEC is nevertheless a cancellable one-shot. Its request ID can
be targeted by OP_CANCEL_REQUEST while the request is waiting for admission
or executing. OP_ENDPOINT_SUB remains a stream lifecycle and is cancelled by
closing that subscription's owning transport, not through the request-cancel
operation.
A published endpoint is a pre-authorized query contract. A logical view is
default-deny and requires an application read grant for execution or
subscribe for a live subscription. Other grant targets and verbs are not
consumable through the current end-user wire. Application errors are sanitized
to stable bodies such as auth failed, auth revoked, not authorized,
invalid parameter, endpoint failed, and limit exceeded: <dimension>.
An endpoint's USING provider is a definition-time claim-schema check, not
runtime provider affinity. Any authenticated provider with compatible claims
can invoke it. Endpoint names also resolve before view names; use a relation
UUID to target a colliding logical view unambiguously.
The application-grant check applies only to end-user view invocation. A named
non-admin operator invoking a claimless endpoint or logical view with
OP_ENDPOINT_EXEC or OP_ENDPOINT_SUB is checked against the canonical
query's scanned base relations in the separate operator-grant catalog. Execution
requires read; subscription requires subscribe. Admin and default-open
implicit operators keep their bypass, and all operator-class calls remain
exempt from application endpoint quotas.
A streaming auth gate may revoke an idle connection by sending status 0x01,
body auth revoked, and a 16-byte all-zero request ID before closing the
transport. Revocation on an active subscription is terminal. There is no
durable resume cursor: reconnect, authenticate again, resubscribe, and replace
the local replica with the new snapshot.
Changing a live-gate claims row affects future invocations only; an existing subscription keeps frozen bindings and identity. Replacing or dropping an endpoint likewise does not terminate an existing subscription, and dropping it removes governor/metering state. Operators must drain those subscriptions before security, query, provider, or limit changes that must invalidate old behavior.
See Browser and platform endpoint clients for framing code, the complete sanitized error table, and reconnect policy.
Response statuses
| Status | Value | Meaning |
|---|---|---|
| OK | 0x00 | Operation succeeded; payload format depends on the operation and negotiated mode. |
| Error | 0x01 | Operation failed; the payload contains the current server diagnostic contract. |
| Snapshot | 0x02 | Full starting result for a stream. |
| Delta | 0x03 | Positional changes to the client's maintained result. |
| Metadata | 0x04 | Server timing metadata for metadata-enabled streams. |
An error status is not a transport retry signal. For a stream, an error is terminal for that subscription.
Wire v1 text results
Without capability negotiation, the connection stays in v1 mode. A one-shot result is UTF-8 TSV:
market\tamount
BTC-USD\t4120
ETH-USD\t2880The first row contains column labels. Metadata-enabled operations add a line
such as
#meta\telapsed_us=<n>\tserver=<id>[\tcache=hit|miss]\tquery_id=<n>.
V1 is intentionally simple but cannot carry full parametric type information. A client commonly sees text rather than a decimal scale, temporal timezone, or vector dimension.
Streaming snapshots and deltas
After the initial snapshot, v1 delta payloads contain positional operations:
| Record | Meaning |
|---|---|
I\t<row>\t<v1>\t<v2>… | Insert a row at a position. |
V\t<row>\t<v1>\t<v2>… | Replace a row. |
U\t<row>\t<column>\t<value> | Update one cell. |
R\t<row> | Remove a row. |
M\t<from>\t<to> | Move a row. |
Apply operations in payload order. If an operation is malformed, out of bounds, or incompatible with the current snapshot, fail closed and rebuild from a new subscription instead of guessing state.
A stream terminates on client cancellation, EOF, transport timeout, server shutdown, source drop, or an error frame. Cross-connection resume is not a current contract. A new subscription begins with a new snapshot that replaces the abandoned client replica.
Wire v2 negotiation
Negotiation happens at most once per connection through operation 0x0F.
ClientHello := magic:0x4E43 schema:u8 flags:u16 count:u8 versions[count]
ServerReply := magic:0x4E43 schema:u8 flags:u16 chosen_version:u8The handshake schema byte is currently 1 or 2; that schema version is
separate from the selected wire version. Both schemas can advertise wire
versions 1 and 2.
| Flag | Client hello | Server acknowledgement | Availability |
|---|---|---|---|
0x0001 | Request binary results | Binary results engaged | Schema 1 and 2 |
0x0002 | Accept raw-LZ4 result frames | Server may emit LZ4 frames | Schema 1 and 2; requires 0x0001 |
0x0004 | Accept typed result metadata envelopes | Server may emit TypedResultEnvelope | Schema 2 only; requires 0x0001 |
The server chooses the highest advertised wire version it supports, with v1 as
its fallback floor, and acknowledges only flags present in that exact hello. A
client must bind the reply to the attempted handshake schema and advertised
versions. A client that can decode v1 should include it in the advertised list.
The server/protocol supports 0x0002, but the current in-tree CLI, console, and
JDBC one-shot paths advertise binary plus typed metadata
(0x0001 | 0x0004) and do not enable LZ4. Their stream paths do not negotiate
these capabilities.
An older server may explicitly reject the capability operation as unsupported. After a schema-2 rejection, close that connection and retry schema 1 on a fresh connection; after a schema-1 rejection, use another fresh connection for plain v1. Negotiate before authentication when v2 is required, then authenticate each fresh socket before a gated operation. Never reuse the rejected socket. A malformed hello or malformed successful reply is fatal and never silently upgrades or downgrades the connection. Old clients that never send a hello remain on v1.
Wire v2 typed frames
V2 keeps the outer request envelope and replaces eligible result/stream bodies with a self-describing columnar frame. Its schema preserves:
- column names, type IDs, nullability, and low-cardinality flags;
- decimal precision and scale;
datetime64precision and timezone;- fixed-string width and
vector(N)dimension; - recursive child fields; and
- Arrow-style validity bitmaps, offsets, dictionaries, and fixed-width value buffers.
Structural integers remain big-endian and fixed-width values are little-endian.
Frame version 2 is a loud incompatibility guard. A client must validate magic,
version, flags, schema depth, lengths, offsets, and row counts before allocating
or exposing values.
Writes and result shapes that are not emitted as a v2 frame may still use a text acknowledgement on a negotiated connection. Decode based on the actual validated payload, not on an assumption that negotiation makes every response binary.
Schema-2 metadata envelope and fallback
When schema 2 acknowledges flags 0x0001 and 0x0004, successful
OP_QUERY_META and OP_QUERY_PARAMS row results carry one NR
TypedResultEnvelope:
magic:0x4E52 version:1 flags:0
elapsed_us:u64be query_id:u64be cache_state:u8
server_len:u16be server:utf8
frame_len:u32be frame:ColumnarFrameThe outer envelope is never compressed; only its nested ColumnarFrame may use
the negotiated LZ4 wrapper. query_id is nonzero and is the engine identity
published in system.queries and later system.query_log. Parameterized
results additionally distinguish cache hit from miss; ordinary metadata queries
use the not-applicable state.
OP_QUERY deliberately remains a bare NY columnar frame or TSV fallback even
on the same schema-2 connection. One-shot endpoint execution also retains plain
TSV; endpoint subscription can use the negotiated stream frame mode but has no
metadata envelope. Classify the payload according to the operation and
negotiated capabilities rather than magic-byte guessing across unrelated
operations.
If either metadata-bearing operation completes with no rows or a shape that the
typed encoder cannot represent, the server renders #meta plus escaped TSV from
the already captured rows. It does not execute the SQL again. Clients must
retain the same elapsed time, numeric query ID, server identity, and
parameter-cache state from that fallback carrier. Bare OP_QUERY fallback is
plain TSV without metadata. This single-execution rule is especially important
for statements with writes or read-side effects.
Query identity and one-shot cancellation
NYXDB exposes two intentionally different identities:
| Identity | Shape | Purpose |
|---|---|---|
| Request ID | 16 opaque bytes in the outer envelope | Correlate frames and target a live one-shot through OP_CANCEL_REQUEST |
| Engine query ID | Nonzero unsigned 64-bit integer in metadata | Join system.queries/system.query_log and issue SQL CANCEL QUERY <id> |
Metadata-enabled v1 responses add the engine identity without changing the TSV schema:
#meta\telapsed_us=<n>\tserver=<id>[\tcache=hit|miss]\tquery_id=<n>To cancel a one-shot before its terminal result, open a separate connection that satisfies the normal auth gate and send:
request op := 0x0A
request payload := target_request_id:bytes[16]
success or miss payload (TSV):
query_id\tcancel_requested
<matched_query_id>\t1
query_id\tcancel_requested
0\t0The cancel request has its own independent request ID. The target payload must
be exactly 16 bytes and nonzero. A unique pending or executing
OP_QUERY, OP_QUERY_META, OP_QUERY_PARAMS, or OP_ENDPOINT_EXEC target
returns 1; a missing, stale, zero, or non-unique target returns 0 and changes
nothing. Registration happens before bounded admission, and cancel dispatch
bypasses query admission, so a saturated queue cannot hide the target or block
the control request.
The server processes ordinary requests sequentially on one connection. Sending cancel behind the blocked statement on that same socket cannot overtake it; use a separate control connection. The acknowledgement means cancellation was requested, not that the original terminal frame has already arrived. Keep the original request registered until its terminal response, and close its transport under a bounded policy if a successful acknowledgement is not followed by one.
Streams, endpoint subscriptions, auth, capability negotiation, ping, and
asynchronous ingest are not 0x0A targets. Close the stream's owning transport
or use its documented query lifecycle instead.
Disconnect-driven cancellation
For production one-shots, raw TCP FIN/reset and WebSocket Close are cooperative
cancellation signals. Query operators and parallel storage reads observe peer
liveness through the same bounded checkpoints used for deadlines and explicit
cancellation. The stable operator diagnostic is
NYXDB_EXEC_CANCELLED: client disconnected, and completion is recorded in
system.query_log.
While a WebSocket one-shot is executing, the liveness path services RFC 6455
Ping/Pong and Close control frames and retains bounded pipelined application
frames for normal dispatch. This is distinct from NYXDB OP_PING (0x08),
which remains the application-level health operation and bypasses query
admission and telemetry.
Binary block ingest
Block ingest bypasses SQL value parsing. A block is column-major and self-describing:
Block := magic:0x4E59 version:1 table:string ncols:u32 nrows:u32 columns[ncols]
Column := name:string type_id:u16 flags:u8 [null_mask] valuesStructural lengths are big-endian; fixed-width values use the engine's little-endian value encoding. V1 block ingest supports the documented scalar set and rejects unsupported nested or unknown types:
- fixed-width:
Bool, signed and unsigned integers from 8 through 256 bits,Float32,Float64,Date,Date32,DateTime,DateTime64, and decimal storage widths from 32 through 256 bits; - variable-width:
String,FixedString,Bytes,UUID,IPv4, andIPv6; - nullable columns: flag bit 0 adds one byte per row to the null mask; a null row contributes no value bytes; and
- unsupported: nested types and unlisted scalar type IDs. Reject them before sending rather than attempting text coercion inside a block.
Every column must describe exactly nrows cells. Fixed-width cells must match
the type's wire width; variable-width cells carry a big-endian length followed
by raw bytes.
Clients must chunk loads:
- compressed or uncompressed wire payload: at most 64 MiB;
- expanded compressed block: at most 512 MiB.
LZ4 ingest prepends the expected expanded length. Both the compressed and expanded limits are validated before the block reaches storage.
Failure and retry contract
- A framing violation, impossible length, request-ID mismatch, malformed negotiation, or invalid typed frame is connection-fatal.
- An SQL error frame is operation-scoped; surface it without reconnect loops.
- A transport close before a DDL/DML response is ambiguous. Reconcile state or use application-level idempotency before repeating the statement.
- Retry connection establishment with bounded exponential backoff, jitter, and an overall deadline.
- Resubscribing after a stream failure creates a new snapshot boundary; it does not resume the previous cursor.
Use Drivers & protocol for the application-level decision table and Capabilities & status for release maturity.