DB
Drivers & Protocol

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.

  1. Open a WebSocket and set binaryType = "arraybuffer".
  2. If wire-v2 negotiation is required, send capability operation 0x0F before authentication. Endpoint execute/subscribe responses currently remain on v1 text payloads even on a negotiated connection.
  3. Send OP_AUTH (0x09) on this socket.
  4. Wait for status 0x00 and payload authenticated\n.
  5. Send OP_ENDPOINT_EXEC (0x0B) or OP_ENDPOINT_SUB (0x0C).
  6. Correlate ordinary responses with the 16-byte request ID.
  7. 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 encodeRequest(op, payloadText) {
  const payload = utf8.encode(payloadText);
  const requestId = crypto.getRandomValues(new Uint8Array(16));
  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" token

If 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.

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:

StatusValueClient action
OK0x00Accept the header or row payload required by the current v1 stream shape.
Snapshot0x02Replace the local replica with the complete starting result.
Delta0x03Apply validated positional mutations in order.
Error0x01Treat 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.

Sanitized application errors

Application-user connections receive stable short errors instead of schema or SQL diagnostics.

Error bodyMeaningRecommended action
endpoint not foundNo matching endpoint or invocable view/UUIDTreat as a deployment or version mismatch; do not loop-retry.
auth failedThe provider or token could not produce a valid claims rowClose 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 revokedA live gate removed the session or its provider was droppedStop using the credential. Obtain a new token before reconnecting.
not authorizedThe role lacks read or subscribe on an invoked viewCorrect the application grant; repeating the same call cannot help.
limit exceeded: <dimension>Static or query-driven endpoint governance denied or terminated the workRespect the named quota. Do not create a reconnect storm.
invalid parameterArity, type, invocation shape, or endpoint mode is wrongFix the client request.
endpoint failedA sanitized engine or channel failureCorrelate with operator-side system.query_log; retry only under an explicit read/reconciliation policy.
operation not permittedThe authenticated end-user socket attempted a non-whitelisted operationFix 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.

TerminationReconnect policy
Clean client cancellationOpen a new socket only when the application wants a new subscription.
Transport loss or server restartBack off, reconnect, authenticate again, subscribe again, and replace state with the new snapshot.
auth revoked or auth failedDo not reconnect with the same credential until the application has refreshed it.
not authorized or invalid parameterDo not retry until configuration or request shape changes.
Rate-limit terminationFollow 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-auth for 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-network ws:// 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 USING provider 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.
  • 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.

On this page