Palm.SurrealDB.Net.Protocol
0.2.1
dotnet add package Palm.SurrealDB.Net.Protocol --version 0.2.1
NuGet\Install-Package Palm.SurrealDB.Net.Protocol -Version 0.2.1
<PackageReference Include="Palm.SurrealDB.Net.Protocol" Version="0.2.1" />
<PackageVersion Include="Palm.SurrealDB.Net.Protocol" Version="0.2.1" />
<PackageReference Include="Palm.SurrealDB.Net.Protocol" />
paket add Palm.SurrealDB.Net.Protocol --version 0.2.1
#r "nuget: Palm.SurrealDB.Net.Protocol, 0.2.1"
#:package Palm.SurrealDB.Net.Protocol@0.2.1
#addin nuget:?package=Palm.SurrealDB.Net.Protocol&version=0.2.1
#tool nuget:?package=Palm.SurrealDB.Net.Protocol&version=0.2.1
Palm.SurrealDB.Net.Protocol
WebSocket and HTTP RPC transports for the Palm.SurrealDB .NET ecosystem for
SurrealDB 3.x. This project owns the wire-level conversation with a
SurrealDB server: request/response framing over the SurrealDB RPC envelope, request correlation,
live-query notification routing, and the two concrete ISurrealConnection implementations
(WebSocketConnection and HttpConnection) that the rest of the ecosystem builds on.
It depends on:
Palm.SurrealDB.Net.Abstractions—ISurrealConnection,SurrealDbOptions,SurrealValue,ConnectionState,LiveNotification, and theSurrealDb*Exceptionhierarchy that this project throws.Palm.SurrealDB.Net.Serialization—ISurrealSerializerand the defaultSurrealCborSerializerused to encode/decode RPC envelopes on the wire.
Why this project exists — and who should use it
Palm.SurrealDB.Net.Protocol is the transport layer, not the consumer-facing API. It exists so the
CBOR-over-WebSocket/HTTP RPC mechanics — request IDs, live-query channel demultiplexing,
reconnection with backoff, session-header replay for the stateless HTTP transport — live in one
place, independent of the typed client and LINQ provider built on top of it.
Most consumers should use Palm.SurrealDB.Net (SurrealDbClient)
directly, not this package. The client owns typed CRUD, QueryAsync, live-query subscriptions,
and DI registration (AddSurrealDb), and selects a transport (WebSocketConnection by default,
HttpConnection for stateless request/response over HTTP) internally. Reach for this project
directly only when you need to construct a transport by hand — e.g. swapping in a custom
HttpClient, driving the RPC connection outside the typed client, or writing tests against the
transport layer in isolation.
Installation
dotnet add package Palm.SurrealDB.Net.Protocol
This project is packable and ships as the Palm.SurrealDB.Net.Protocol NuGet package. An earlier
version of this section said the opposite, citing a true observation — the .csproj sets neither
PackageId nor IsPackable — in support of a false conclusion: packability comes from the SDK
default and Directory.Build.props, and the only projects that opt
out are the three that set IsPackable=false. PackableSetGuardTests pins all 25 packable ids,
and this is one of them.
Targets net10.0 only. The package is proprietary and sets PackageRequireLicenseAcceptance,
so NuGet prompts before installing — see LICENSE.
Nothing is on NuGet.org until the first release runs (see
docs/RELEASING.md); until then, reference the project directly:
<ItemGroup>
<ProjectReference Include="path/to/src/Palm.SurrealDB.Net.Protocol/Palm.SurrealDB.Net.Protocol.csproj" />
</ItemGroup>
Public API surface
Per PublicAPI.Unshipped.txt (the analyzer-enforced source of truth for this project's public
surface — see AGENTS.md), the public API is deliberately small: two sealed
classes, both implementing ISurrealConnection from Palm.SurrealDB.Net.Abstractions. Everything
else in this project — the RPC envelope codec, RpcDispatcher (request/response correlation and
live-query channel routing), RpcServerMessage, RpcError, StatementTimeParser — is internal.
Palm.SurrealDB.Net has InternalsVisibleTo access to consume QueryResultParser; the
envelope/dispatcher internals stay hidden even from that sibling project.
WebSocketConnection
public sealed class WebSocketConnection : ISurrealConnection
{
public WebSocketConnection(SurrealDbOptions options, ISurrealSerializer? serializer = null);
public ConnectionState State { get; }
public bool SupportsLiveQueries { get; } // true
public Func<ISurrealConnection, CancellationToken, Task>? SessionRestore { get; set; }
public Task ConnectAsync(CancellationToken cancellationToken = default);
public Task<SurrealValue> SendAsync(
string method,
IReadOnlyList<SurrealValue> parameters,
CancellationToken cancellationToken = default);
public IAsyncEnumerable<LiveNotification> SubscribeAsync(
Guid liveQueryId,
CancellationToken cancellationToken = default);
public ValueTask DisposeAsync();
}
A multiplexed transport: many in-flight SendAsync calls share one socket, correlated by request
ID via the internal RpcDispatcher; live-query notifications are routed to per-query channels
keyed by the query's Guid. The endpoint scheme is normalized on construction — http/https
become ws/wss, and a bare host gets /rpc appended if no path is present
(WebSocketConnection.NormalizeEndpoint, internal).
Constructor notes:
optionsis validated eagerly (SurrealDbOptions.Validate()) — invalid options throwSurrealDbConfigurationExceptionbefore any I/O happens.serializerdefaults toSurrealCborSerializer.Instance(CBOR over thecborWebSocket subprotocol).
Member notes:
ConnectAsyncopens the socket withinSurrealDbOptions.ConnectTimeout(default 5s) and starts a background receive loop. Calling it while alreadyConnectedis a no-op. Connection failures during the handshake surface as a retriableSurrealDbConnectionException.SendAsyncrequiresState == Connected(or, during an in-progress session restore,Reconnecting) — otherwise it throws a retriableSurrealDbConnectionExceptiontelling the caller to callConnectAsyncfirst. Each call waits up toSurrealDbOptions.RequestTimeout(default 30s) for a response; on timeout the pending request is abandoned and a retriableSurrealDbConnectionExceptionis thrown. CancellingcancellationTokenabandons the request and rethrowsOperationCanceledException.SubscribeAsyncreturns anIAsyncEnumerable<LiveNotification>backed by a per-query channel. On connection loss, all live-query channels complete with the connection error — consumers must restart their live queries after a reconnect; live-query state itself is not replayed automatically.SessionRestoreis invoked after a successful automatic reconnect, beforeStatereturns toConnected— set it to re-authenticate and re-usethe namespace/database, since a reconnect establishes a brand-new socket with no server-side session. While aSessionRestorecallback is running,SendAsyncis additionally permitted in theReconnectingstate so the restore logic itself can issue RPCs.- Reconnection: driven by
SurrealDbOptions.Reconnect(ReconnectOptions—Enabled,InitialDelay,MaxDelay,MaxAttempts,JitterFactor). On unexpected socket loss, all pending requests fail viaRpcDispatcher.FailAll, and ifReconnect.Enabledis true the transport retries with exponential backoff (InitialDelay * 2^attempt, capped atMaxDelay, jittered byJitterFactor) untilMaxAttemptsis reached (or indefinitely ifMaxAttemptsisnull). Exhausting attempts leavesState == Disconnected. DisposeAsynccancels the receive loop, attempts a graceful WebSocket close (2s budget), waits for the receive loop to exit, fails any still-pending requests with aSurrealDbConnectionException, and releases all internal synchronization primitives. Safe to call multiple times.
HttpConnection
public sealed class HttpConnection : ISurrealConnection
{
public HttpConnection(
SurrealDbOptions options,
ISurrealSerializer? serializer = null,
HttpClient? httpClient = null);
public ConnectionState State { get; }
public bool SupportsLiveQueries { get; } // false
public Task ConnectAsync(CancellationToken cancellationToken = default);
public Task<SurrealValue> SendAsync(
string method,
IReadOnlyList<SurrealValue> parameters,
CancellationToken cancellationToken = default);
public IAsyncEnumerable<LiveNotification> SubscribeAsync(
Guid liveQueryId,
CancellationToken cancellationToken = default);
public ValueTask DisposeAsync();
}
A stateless transport over POST /rpc with CBOR request/response bodies. Because HTTP has no
persistent session, namespace/database selection and auth are held client-side and replayed as
headers (Surreal-NS, Surreal-DB, Authorization) on every request.
Constructor notes:
- If
httpClientis omitted,HttpConnectioncreates and owns its ownHttpClient(disposed onDisposeAsync); pass an externally owned client to share connection pooling — in that caseHttpConnectiondoes not dispose it. - The endpoint scheme is normalized the opposite direction from
WebSocketConnection:ws/wssbecomehttp/https, with the same/rpcpath default. optionsis validated eagerly, same asWebSocketConnection.
Member notes:
ConnectAsyncis synchronous under the hood — there is no persistent connection to establish, so it just marksState = Connected. Reachability is only proven on the first real request.SendAsyncspecial-cases several RPC methods client-side rather than forwarding them verbatim:"use"updates the locally held namespace/database and forwards a realuseRPC to the server, because theSurreal-NS/Surreal-DBheaders only ever select an existing namespace/database on later requests — they never auto-create one the way a liveusecall does."authenticate"stores the supplied bearer token locally (thrown asSurrealDbAuthenticationExceptionif the parameter isn't a token string) rather than issuing a request; the token is validated by the server lazily, on the next real request."invalidate"clears the locally held token."let"/"unset"(session variables) and"live"/"kill"(live queries) throwSurrealDbCapabilityException— both require server-side session state that a stateless HTTP transport cannot provide. UseWebSocketConnectionfor these.- All other methods (including
"signin") are POSTed to the server; a successful"signin"response is captured as the bearer token for subsequent requests. - When no bearer token is held, requests fall back to HTTP Basic auth only when
SurrealDbOptions.CredentialsisRootCredentials— other credential kinds (e.g. scope/record users) mustsigninexplicitly to obtain a bearer token first. - Non-success HTTP status codes throw
SurrealDbConnectionException, marked retriable for5xxresponses and non-retriable otherwise. An RPC-level error in a successful HTTP response throwsSurrealDbRpcException(ErrorCode,Details,Causepopulated from the server's error envelope).
SubscribeAsyncalways throwsSurrealDbCapabilityException— live queries are a WebSocket-only feature;SupportsLiveQueriesisfalsefor exactly this reason.DisposeAsyncdisposes the ownedHttpClient(if this instance created it) and marksState = Disconnected. Safe to call multiple times.
Internal architecture (for context only)
The rest of the project is internal and not part of the public contract, but a brief map helps
when reading source that lands in this package:
RpcEnvelopesbuilds outbound request envelopes (id,method,params) and parses inbound server messages into a normalizedRpcServerMessage(result or error, never both).RpcDispatcheris the correlation core shared conceptually by both transports (though onlyWebSocketConnectionuses it directly, since it is the only transport with a persistent, multiplexed connection): it hands out monotonically increasing request IDs, tracks pending requests as awaitable completions, demultiplexes live-query notifications into per-Guidbounded channels (capacity fromSurrealDbOptions.LiveQueryChannelCapacity), and can fail every pending request/channel at once (FailAll) when the connection is lost or disposed.RpcServerMessage/RpcErrorare the parsed shapes of a server response — a result payload or a structured error (Code,Kind,Message,Details,Cause).QueryResultParserturns a rawqueryRPC result into the ecosystem's typed query-result shape; it is the one internal type explicitly exposed toPalm.SurrealDB.NetviaInternalsVisibleTo, since the client needs it to implementQueryAsync.StatementTimeParserparses the per-statementtimefield SurrealDB returns alongside query results (e.g."1.2ms") into aTimeSpan.
In short: WebSocketConnection is a stateful, multiplexed, reconnecting transport built around
RpcDispatcher; HttpConnection is a stateless, one-request-per-call transport that fakes session
continuity with headers and a locally cached token. Both speak the same CBOR RPC envelope via
RpcEnvelopes and the shared ISurrealSerializer from Palm.SurrealDB.Net.Serialization.
Error surfaces
Both connection types throw from the same SurrealDb*Exception hierarchy defined in
Palm.SurrealDB.Net.Abstractions:
| Exception | Thrown when |
|---|---|
SurrealDbConfigurationException |
SurrealDbOptions.Validate() rejects the options passed to the constructor. |
SurrealDbConnectionException (IsRetriable) |
Connect/send failures, socket loss, request timeout, non-2xx/5xx HTTP responses. |
SurrealDbAuthenticationException |
HttpConnection.SendAsync("authenticate", ...) receives a non-string token parameter. |
SurrealDbCapabilityException |
A transport is asked to do something it structurally cannot: session variables or live queries over HTTP. |
SurrealDbRpcException (ErrorCode, Details, Cause) |
The server returns a structured RPC error for an otherwise successful request/response. |
Related projects
Palm.SurrealDB.Net.Abstractions— core value types and contracts this project implements against.Palm.SurrealDB.Net.Serialization— the CBOR codec used to encode/decode every RPC envelope.Palm.SurrealDB.Net.SurrealQL— the SurrealQL AST, emitter, and parser; not a dependency of this project, but the source of the query text sent over these transports.Palm.SurrealDB.Net— the typed client (SurrealDbClient) most consumers should use instead of this project directly.Palm.SurrealDB.Net.Linq— theIQueryable<T>LINQ-to-SurrealQL provider, built on top ofPalm.SurrealDB.Net.Palm.SurrealDB.FSharp— the idiomatic F# DSL over the SurrealQL AST.- Root README — ecosystem overview, quick start, build/test instructions.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Palm.SurrealDB.Net.Abstractions (>= 0.2.1)
- Palm.SurrealDB.Net.Serialization (>= 0.2.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Palm.SurrealDB.Net.Protocol:
| Package | Downloads |
|---|---|
|
Palm.SurrealDB.Net
SurrealDB client for .NET — the Palm.SurrealDB ecosystem's primary package. |
GitHub repositories
This package is not used by any popular GitHub repositories.