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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Palm.SurrealDB.Net.Protocol" Version="0.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Palm.SurrealDB.Net.Protocol" Version="0.2.1" />
                    
Directory.Packages.props
<PackageReference Include="Palm.SurrealDB.Net.Protocol" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Palm.SurrealDB.Net.Protocol --version 0.2.1
                    
#r "nuget: Palm.SurrealDB.Net.Protocol, 0.2.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Palm.SurrealDB.Net.Protocol@0.2.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Palm.SurrealDB.Net.Protocol&version=0.2.1
                    
Install as a Cake Addin
#tool nuget:?package=Palm.SurrealDB.Net.Protocol&version=0.2.1
                    
Install as a Cake Tool

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 the SurrealDb*Exception hierarchy that this project throws.
  • Palm.SurrealDB.Net.Serialization — ISurrealSerializer and the default SurrealCborSerializer used 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:

  • options is validated eagerly (SurrealDbOptions.Validate()) — invalid options throw SurrealDbConfigurationException before any I/O happens.
  • serializer defaults to SurrealCborSerializer.Instance (CBOR over the cbor WebSocket subprotocol).

Member notes:

  • ConnectAsync opens the socket within SurrealDbOptions.ConnectTimeout (default 5s) and starts a background receive loop. Calling it while already Connected is a no-op. Connection failures during the handshake surface as a retriable SurrealDbConnectionException.
  • SendAsync requires State == Connected (or, during an in-progress session restore, Reconnecting) — otherwise it throws a retriable SurrealDbConnectionException telling the caller to call ConnectAsync first. Each call waits up to SurrealDbOptions.RequestTimeout (default 30s) for a response; on timeout the pending request is abandoned and a retriable SurrealDbConnectionException is thrown. Cancelling cancellationToken abandons the request and rethrows OperationCanceledException.
  • SubscribeAsync returns an IAsyncEnumerable<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.
  • SessionRestore is invoked after a successful automatic reconnect, before State returns to Connected — set it to re-authenticate and re-use the namespace/database, since a reconnect establishes a brand-new socket with no server-side session. While a SessionRestore callback is running, SendAsync is additionally permitted in the Reconnecting state 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 via RpcDispatcher.FailAll, and if Reconnect.Enabled is true the transport retries with exponential backoff (InitialDelay * 2^attempt, capped at MaxDelay, jittered by JitterFactor) until MaxAttempts is reached (or indefinitely if MaxAttempts is null). Exhausting attempts leaves State == Disconnected.
  • DisposeAsync cancels the receive loop, attempts a graceful WebSocket close (2s budget), waits for the receive loop to exit, fails any still-pending requests with a SurrealDbConnectionException, 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 httpClient is omitted, HttpConnection creates and owns its own HttpClient (disposed on DisposeAsync); pass an externally owned client to share connection pooling — in that case HttpConnection does not dispose it.
  • The endpoint scheme is normalized the opposite direction from WebSocketConnection: ws/wss become http/https, with the same /rpc path default.
  • options is validated eagerly, same as WebSocketConnection.

Member notes:

  • ConnectAsync is synchronous under the hood — there is no persistent connection to establish, so it just marks State = Connected. Reachability is only proven on the first real request.
  • SendAsync special-cases several RPC methods client-side rather than forwarding them verbatim:
    • "use" updates the locally held namespace/database and forwards a real use RPC to the server, because the Surreal-NS/Surreal-DB headers only ever select an existing namespace/database on later requests — they never auto-create one the way a live use call does.
    • "authenticate" stores the supplied bearer token locally (thrown as SurrealDbAuthenticationException if 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) throw SurrealDbCapabilityException — both require server-side session state that a stateless HTTP transport cannot provide. Use WebSocketConnection for 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.Credentials is RootCredentials — other credential kinds (e.g. scope/record users) must signin explicitly to obtain a bearer token first.
    • Non-success HTTP status codes throw SurrealDbConnectionException, marked retriable for 5xx responses and non-retriable otherwise. An RPC-level error in a successful HTTP response throws SurrealDbRpcException (ErrorCode, Details, Cause populated from the server's error envelope).
  • SubscribeAsync always throws SurrealDbCapabilityException — live queries are a WebSocket-only feature; SupportsLiveQueries is false for exactly this reason.
  • DisposeAsync disposes the owned HttpClient (if this instance created it) and marks State = 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:

  • RpcEnvelopes builds outbound request envelopes (id, method, params) and parses inbound server messages into a normalized RpcServerMessage (result or error, never both).
  • RpcDispatcher is the correlation core shared conceptually by both transports (though only WebSocketConnection uses 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-Guid bounded channels (capacity from SurrealDbOptions.LiveQueryChannelCapacity), and can fail every pending request/channel at once (FailAll) when the connection is lost or disposed.
  • RpcServerMessage / RpcError are the parsed shapes of a server response — a result payload or a structured error (Code, Kind, Message, Details, Cause).
  • QueryResultParser turns a raw query RPC result into the ecosystem's typed query-result shape; it is the one internal type explicitly exposed to Palm.SurrealDB.Net via InternalsVisibleTo, since the client needs it to implement QueryAsync.
  • StatementTimeParser parses the per-statement time field SurrealDB returns alongside query results (e.g. "1.2ms") into a TimeSpan.

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.
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
0.2.1 173 9/5/2026
0.2.0 181 9/3/2026