Browser and platform endpoint clients
Implement the experimental WebSocket auth, endpoint execution, subscription, error, revocation, and reconnect contract without assuming an SDK.
A controlled browser, mobile, or edge integration can call an operator-published NYXDB endpoint directly over WebSocket. This page documents the wire contract; it does not claim that NYXDB ships a browser, mobile, or edge SDK.
This is an Experimental protocol surface. The native server exposes
ws://, not wss://, and does not enforce a public zero-trust boundary. For
any untrusted client, place the listener behind reviewed TLS termination and
Origin/network policy, and start NYXDB with --require-auth. Without that
flag, a client can skip OP_AUTH and remain an implicit operator with raw SQL
and write access. Pin the engine revision and keep protocol conformance tests
with the client.
Connection sequence
Use one authenticated connection for sequential one-shot calls and a dedicated connection for each live subscription.
- Open a WebSocket and set
binaryType = "arraybuffer". - If wire-v2 negotiation is required, send capability operation
0x0Fbefore authentication. Endpoint execute remains TSV; endpoint subscriptions may use negotiated typed snapshot/delta frames. - Send
OP_AUTH(0x09) on this socket. - Wait for status
0x00and payloadauthenticated\n. - Send
OP_ENDPOINT_EXEC(0x0B) orOP_ENDPOINT_SUB(0x0C). - Correlate ordinary responses with the 16-byte request ID.
- On reconnect, repeat authentication before issuing the endpoint operation.
Authentication is connection-scoped. A token accepted on one socket does not authenticate a pool, another tab, or the next subscription socket.
Authenticate once per fresh socket. OP_AUTH is not a logout or principal
replacement operation: if a re-auth attempt fails, the connection keeps its
previous principal. Close the socket and open a new one whenever the provider,
token, or principal changes.
Outer frame
Each WebSocket binary message contains one complete engine frame. Structural lengths use network byte order.
Request := op:u8 request_id:16 payload_len:u32be payload[payload_len]
Response := request_id:16 status:u8 payload_len:u32be payload[payload_len]The browser can construct a request without a client library:
const utf8 = new TextEncoder();
function randomRequestId() {
let requestId;
do {
requestId = crypto.getRandomValues(new Uint8Array(16));
} while (requestId.every((byte) => byte === 0));
return requestId;
}
function encodeRequest(op, payloadText) {
const payload = utf8.encode(payloadText);
const requestId = randomRequestId();
const frame = new Uint8Array(1 + 16 + 4 + payload.length);
frame[0] = op;
frame.set(requestId, 1);
new DataView(frame.buffer).setUint32(17, payload.length, false);
frame.set(payload, 21);
return { requestId, frame };
}This helper shows layout only. A production integration must also validate response lengths, cap allocations, compare request IDs, reject unknown statuses, bound all waits, and close on a malformed or contradictory frame.
Authenticate an application user
The end-user auth payload is the provider name, one NUL byte, and the opaque token:
OP_AUTH value := 0x09
OP_AUTH payload := provider_name "\0" tokenIf exactly one provider is registered, an empty provider name selects it. Name the provider explicitly when the client contract should remain stable as more providers are added.
const auth = encodeRequest(0x09, `app_sessions\0${token}`);
socket.send(auth.frame);A successful response has status 0x00 and body authenticated\n. Every
provider miss, ambiguous provider, invalid token, zero-or-many lookup result,
or provider execution failure is deliberately collapsed to status 0x01 with
body auth failed.
Operator authentication uses a different payload shape,
\0system\0user\0password. Do not send that shape from an application-user
client.
The provider referenced by an endpoint definition validates its claims schema; it is not an invocation-affinity check. A session admitted by another provider can call that endpoint when it supplies compatible claim names and types. The stored endpoint predicate must enforce every tenant and visibility boundary.
Execute a published query
The execution payload begins with an endpoint name followed by zero or more NUL-delimited client parameters in declaration order:
OP_ENDPOINT_EXEC value := 0x0B
OP_ENDPOINT_EXEC payload := endpoint_name ( "\0" parameter )*const call = encodeRequest(0x0b, `my_fills\0BTC-USD`);
socket.send(call.frame);Status 0x00 carries a v1 TSV header and rows, including after wire-v2
negotiation. Parameters are text on this wire path and are validated against
the endpoint signature. The v1 parameter form cannot distinguish a SQL NULL
from a text value.
Send client parameters only. Claims declared by the endpoint are appended by the server from the authenticated row. An extra value does not override a claim; it makes the parameter shape invalid.
The same operation can invoke a parameterized logical view by name or relation
UUID. A view requires an application read grant for the current role claim.
An endpoint is the pre-authorized bundle and does not consult the view grant
catalog.
Endpoint names resolve before logical-view names. A collision therefore selects the endpoint and its pre-authorized behavior, not the view grant the client may expect. Keep published names collision-free. Use the relation UUID when the client must target a logical view unambiguously.
Cancel an executing one-shot
Keep the 16-byte request ID for every OP_ENDPOINT_EXEC. If the local deadline
expires, open a separate WebSocket, repeat capability negotiation when needed,
authenticate it with OP_AUTH, and send OP_CANCEL_REQUEST (0x0A) with the
original request ID as its exact 16-byte binary payload.
function encodeRawRequest(op, payload) {
const requestId = randomRequestId();
const frame = new Uint8Array(1 + 16 + 4 + payload.length);
frame[0] = op;
frame.set(requestId, 1);
new DataView(frame.buffer).setUint32(17, payload.length, false);
frame.set(payload, 21);
return { requestId, frame };
}
const cancel = encodeRawRequest(0x0a, call.requestId);
controlSocket.send(cancel.frame);The server returns status 0x00 with one of these stable TSV rows:
query_id\tcancel_requested
42\t1
query_id\tcancel_requested
0\t01 means one pending or executing request was found and cooperative
cancellation was requested. 0 means the target was missing, stale, zero, or
not uniquely registered; it is not an error and does not change another query.
Registration occurs before bounded query admission, so cancellation can reach a
queued endpoint execution, and the cancel control operation itself does not wait
behind query admission.
Do not send cancel behind the target on the same WebSocket: the server processes
ordinary requests sequentially per connection, so it cannot overtake the active
one-shot. The acknowledgement also does not replace the original terminal
response. Keep the original request registered until it ends, and close its
socket under a bounded policy if a successful cancellation acknowledgement is
not followed by a terminal response. OP_ENDPOINT_SUB is not a request-cancel
target; close that subscription's dedicated socket instead.
Subscribe to a published query
OP_ENDPOINT_SUB uses the same payload shape:
OP_ENDPOINT_SUB value := 0x0C
OP_ENDPOINT_SUB payload := endpoint_name ( "\0" parameter )*The target endpoint must use AS STREAM SELECT. A one-shot endpoint returns
invalid parameter when used with the subscription operation. A granted view
can also be subscribed through this operation when the role has subscribe.
The response sequence uses these statuses:
| Status | Value | Client action |
|---|---|---|
| OK | 0x00 | Accept the setup/header payload defined by the negotiated stream mode. |
| Snapshot | 0x02 | Replace the local replica with the complete v1 or typed starting result. |
| Delta | 0x03 | Apply validated v1 or typed positional mutations in order. |
| Error | 0x01 | Treat the subscription as terminal. |
Do not assume a single row-only shape for every stream. The maintained stream contract can carry a snapshot plus positional deltas. Validate every row and index before applying it; on an impossible delta, close and rebuild from a new subscription rather than guessing state.
When this endpoint client negotiates binary results, eligible snapshot and delta
bodies use the wire-v2 columnar layouts; an unsupported shape can still fall
back to text. LZ4 is possible only when the client explicitly requests that
capability. The current in-tree console/CLI one-shot paths request typed
results and schema-2 metadata without LZ4, but their stream paths do not perform
capability negotiation. Endpoint subscriptions never carry STATUS_META or an
engine query ID on this path.
Sanitized application errors
Application-user connections receive stable short errors instead of schema or SQL diagnostics.
| Error body | Meaning | Recommended action |
|---|---|---|
endpoint not found | No matching endpoint or invocable view/UUID | Treat as a deployment or version mismatch; do not loop-retry. |
auth failed | The provider or token could not produce a valid claims row | Close the socket. Refresh or replace the credential, then authenticate exactly once on a fresh connection; failed re-auth does not clear a prior principal. |
auth revoked | A live gate removed the session or its provider was dropped | Stop using the credential. Obtain a new token before reconnecting. |
not authorized | The role lacks read or subscribe on an invoked view | Correct the application grant; repeating the same call cannot help. |
limit exceeded: <dimension> | Static or query-driven endpoint governance denied or terminated the work | Respect the named quota. Do not create a reconnect storm. |
invalid parameter | Arity, type, invocation shape, or endpoint mode is wrong | Fix the client request. |
endpoint failed | A sanitized engine or channel failure | Correlate with operator-side system.query_log; retry only under an explicit read/reconciliation policy. |
operation not permitted | The authenticated end-user socket attempted a non-whitelisted operation | Fix the client. End users cannot run raw SQL or block ingest. |
The server may also return bounded admission errors such as
server busy: query admission rejected or server busy: query admission timed out. Those are back-pressure signals: use jittered bounded backoff and an
overall deadline.
Revocation frames
A streaming auth gate can revoke an idle connection without waiting for a new request. The server sends an unsolicited error frame with:
- status
0x01; - body
auth revoked; and - a 16-byte all-zero request ID;
then drops the transport. On a live subscription, revocation is its terminal error and the ordinary detach path releases the subscriber and quota state.
Updating claims in a streaming auth-gate row is not a live rebind. An existing
subscription keeps its original bindings, user_id, tenant, and role. For an
identity or authorization change, remove the old token to trigger revocation,
issue a new token, and require a fresh connection and subscription.
Your dispatcher must therefore accept a valid server control frame that does not match an in-flight request only for this documented zero-ID revocation case. Any other unmatched response is protocol-fatal.
Reconnect and reconciliation
The current engine does not issue a durable resume cursor.
| Termination | Reconnect policy |
|---|---|
| Cancelled one-shot | Wait for or bound the original terminal response. Retry only when the application has established that repeating the read is safe. |
| Clean client cancellation | Open a new socket only when the application wants a new subscription. |
| Transport loss or server restart | Back off, reconnect, authenticate again, subscribe again, and replace state with the new snapshot. |
auth revoked or auth failed | Do not reconnect with the same credential until the application has refreshed it. |
not authorized or invalid parameter | Do not retry until configuration or request shape changes. |
| Rate-limit termination | Follow the quota policy; repeated immediate reconnects can create further denials. |
An endpoint replace or drop also does not terminate an established subscription. The old query and bindings continue, and a drop removes its governor/metering state. Treat an operator-announced endpoint policy change as a drain boundary: close the old subscription and establish a fresh one only after the reviewed definition is active.
Persist the last application state only if the application can reconcile it against a replacement snapshot. Never append a new snapshot to an abandoned replica as though it were a continuation.
Security checklist for platform clients
- Start NYXDB with
--require-authfor every browser-facing or otherwise untrusted listener. Treat a missing flag as a launch-blocking error. - Use a reviewed
wss://edge that terminates TLS before forwarding to the trusted-networkws://listener. - Restrict which origins and networks can reach that edge; the engine transport does not provide a public Origin allow-list contract.
- Keep tokens out of URLs and logs. They belong only in the binary auth frame.
- Never change principal or token by re-authenticating an existing socket; close it and authenticate once on a fresh connection.
- Configure every provider to return a stable, non-secret
user_id; otherwise the bearer token becomes the fallback identity and can appear in subscriber and governance telemetry. - Do not treat an endpoint's
USINGprovider as runtime authorization affinity; validate every required claim and enforce tenant visibility in the stored SQL. - Keep endpoint and view names collision-free, or invoke the intended logical view by UUID.
- Use one fresh random 16-byte request ID per operation.
- Set connect, auth, response, idle, and total-operation deadlines.
- Cap frame lengths before allocating and reject text WebSocket messages.
- Authenticate every newly opened socket before the endpoint operation.
- Send one-shot cancellation on a separately authenticated control socket; do not queue it behind the target request.
- Pin the engine revision and test auth, execution, snapshot/delta handling, revocation, quota errors, and reconnect behavior before an upgrade.
For the SQL objects behind this flow, see Application access SQL. For the complete frame catalog, see Experimental wire protocol.
JDBC driver
Build and use the experimental Java 17 NYXDB JDBC driver, including URLs, typed v2 results, metadata, limitations, retries, and version compatibility.
Query identity, cancellation, and retries
Correlate wire requests with engine queries, cancel queued or executing work, handle disconnect-driven cancellation, and retry without duplicating side effects.