Palm.SurrealDB.Net.Abstractions 0.2.1

dotnet add package Palm.SurrealDB.Net.Abstractions --version 0.2.1
                    
NuGet\Install-Package Palm.SurrealDB.Net.Abstractions -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.Abstractions" 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.Abstractions" Version="0.2.1" />
                    
Directory.Packages.props
<PackageReference Include="Palm.SurrealDB.Net.Abstractions" />
                    
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.Abstractions --version 0.2.1
                    
#r "nuget: Palm.SurrealDB.Net.Abstractions, 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.Abstractions@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.Abstractions&version=0.2.1
                    
Install as a Cake Addin
#tool nuget:?package=Palm.SurrealDB.Net.Abstractions&version=0.2.1
                    
Install as a Cake Tool

Palm.SurrealDB.Net.Abstractions

Core value types and contracts for the Palm.SurrealDB .NET ecosystem for SurrealDB 3.x. This is the leaf package: it depends on nothing else in the ecosystem, and every other Palm.SurrealDB.* project depends on it. It defines the vocabulary everything else speaks — RecordId, Table, SurrealValue, connection options, credential shapes, the client and type-mapper interfaces, entity-mapping attributes, live-query change types, and the exception hierarchy — with no wire protocol, no serialization codec, and no network code of its own.

Why this project exists

Every other project in the ecosystem — the CBOR serializer, the WebSocket/HTTP transport, the SurrealQL AST and LINQ provider, the client implementation — needs to agree on what a record id is, what a SurrealDB value looks like in memory, and what shape a connection's options and credentials take. Rather than let each project define its own version of these types (and drift), they live here once, as plain value types and interfaces with no external dependencies. This also makes the project safe to reference from tooling, tests, or a future source-generator without pulling in networking or serialization code.

Installation

dotnet add package Palm.SurrealDB.Net.Abstractions

This project is packable and ships as the Palm.SurrealDB.Net.Abstractions 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.Abstractions/Palm.SurrealDB.Net.Abstractions.csproj" />
</ItemGroup>
Project Purpose
Palm.SurrealDB.Net.Serialization CBOR codec for SurrealValue and the reflection-based ISurrealTypeMapper.
Palm.SurrealDB.Net.Protocol WebSocket/HTTP RPC transports implementing ISurrealConnection.
Palm.SurrealDB.Net.SurrealQL The SurrealQL AST, emitter, parser, and formatter.
Palm.SurrealDB.Net SurrealDbClient, the ISurrealClient implementation, and DI registration.
Palm.SurrealDB.Net.Linq The IQueryable<T>-to-SurrealQL LINQ provider.
Root README Ecosystem overview, quick start, and build/test commands.

API reference

Every public type below is drawn directly from source and from PublicAPI.Shipped.txt / PublicAPI.Unshipped.txt, the analyzer-enforced baseline of this project's public surface. All types live in the Palm.SurrealDB.Net namespace.

Identifiers

Table
public readonly record struct Table
{
    public string Name { get; }
    public Table(string name);
    public override string ToString();
    public static implicit operator Table(string name);
    public RecordId Id(RecordIdKey key);
}

Identifies a SurrealDB table by its raw, unescaped name. The constructor throws ArgumentNullException for a null name and ArgumentException for empty or whitespace-only names. default(Table) is invalid (its Name is null) — always construct via the constructor or the implicit string conversion. Id(RecordIdKey) is the idiomatic way to build a RecordId in this table: new Table("person").Id(1).

RecordId
public readonly struct RecordId : IEquatable<RecordId>
{
    public RecordId(Table table, RecordIdKey key);
    public Table Table { get; }
    public RecordIdKey Key { get; }
    public bool Equals(RecordId other);
    public static bool operator ==(RecordId left, RecordId right);
    public static bool operator !=(RecordId left, RecordId right);
    public static RecordId Parse(string s);
    public static bool TryParse(string? s, out RecordId result);
    public override string ToString();
}

A SurrealDB record identifier: a table plus a key. default(RecordId) is invalid; always construct via the constructor.

  • Parse/TryParse accept canonical table:key text. Parts are bare identifiers ([A-Za-z0-9_]+), ⟨…⟩-escaped, or backtick-escaped. A bare key of only digits (with an optional leading -) parses as an integer key; escaped keys always parse as string keys. Array and object keys have no text form and are rejected by TryParse. A whitespace-only escaped table part (⟨ ⟩:x) is rejected — a table name cannot be whitespace — while a whitespace-only escaped key (x:⟨ ⟩) is valid, because RecordIdKey rejects only null/empty. Parse throws FormatException on invalid input (via ArgumentNullException first if s is null).
  • ToString formats the canonical table:key form. A UUID key parses back via TryParse as a RecordIdKeyKind.String key, not RecordIdKeyKind.Uuid — canonical text has no UUID-vs-string distinction; the CBOR wire form is the round-trip-preserving representation. Array and object keys produce a diagnostic-only form (table:[N items]), since their real SurrealQL literal form belongs to the AST layer, not this text format. ToString throws InvalidOperationException if Key holds an unrecognized RecordIdKeyKind (a defensive guard against future enum growth, not a normal failure mode).
RecordIdKey / RecordIdKeyKind
public enum RecordIdKeyKind
{
    String = 0,
    Int64,
    Uuid,
    Array,
    Object,
}

public readonly struct RecordIdKey : IEquatable<RecordIdKey>
{
    public RecordIdKeyKind Kind { get; }
    public static RecordIdKey From(string key);
    public static RecordIdKey From(long key);
    public static RecordIdKey From(Guid key);
    public static RecordIdKey FromArray(IEnumerable<SurrealValue> items);
    public static RecordIdKey FromObject(IEnumerable<KeyValuePair<string, SurrealValue>> fields);
    public string AsString();
    public long AsInt64();
    public Guid AsUuid();
    public ImmutableArray<SurrealValue> AsArray();
    public ImmutableDictionary<string, SurrealValue> AsObject();
    public bool Equals(RecordIdKey other);
    public static bool operator ==(RecordIdKey left, RecordIdKey right);
    public static bool operator !=(RecordIdKey left, RecordIdKey right);
    public static implicit operator RecordIdKey(string key);
    public static implicit operator RecordIdKey(long key);
    public static implicit operator RecordIdKey(Guid key);
}

The key portion of a RecordId — a tagged union over string, long, Guid, an ImmutableArray<SurrealValue>, or an ImmutableDictionary<string, SurrealValue>. default(RecordIdKey) is invalid; always construct via the From* factories or an implicit conversion.

  • From(string) throws ArgumentException for an empty (but not null-checked separately — ArgumentException.ThrowIfNullOrEmpty covers both) string.
  • Each As* accessor throws InvalidOperationException ("The key holds a {Kind}, not a {requested}.") when called against the wrong Kind.
  • Implicit conversions exist for string, long, and Guid; array and object keys must go through FromArray/FromObject explicitly.

Values

SurrealValue / SurrealValueKind
public readonly struct SurrealValue : IEquatable<SurrealValue>
{
    public SurrealValueKind Kind { get; }
    public static SurrealValue None { get; }   // == default(SurrealValue)
    public static SurrealValue Null { get; }

    public static SurrealValue From(bool value);
    public static SurrealValue From(long value);
    public static SurrealValue From(double value);
    public static SurrealValue From(decimal value);
    public static SurrealValue From(string value);
    public static SurrealValue From(DateTimeOffset value);
    public static SurrealValue From(SurrealDuration value);
    public static SurrealValue From(Guid value);
    public static SurrealValue From(ReadOnlyMemory<byte> value);
    public static SurrealValue From(Table value);
    public static SurrealValue From(Geometry value);
    public static SurrealValue From(SurrealRange value);
    public static SurrealValue From(RecordId value);
    public static SurrealValue FromArray(IEnumerable<SurrealValue> items);
    public static SurrealValue FromObject(IEnumerable<KeyValuePair<string, SurrealValue>> fields);

    public bool GetBoolean();
    public long GetInt64();
    public double GetDouble();
    public decimal GetDecimal();
    public string GetString();
    public DateTimeOffset GetDateTime();
    public SurrealDuration GetDuration();
    public Guid GetUuid();
    public ReadOnlyMemory<byte> GetBytes();
    public Table GetTable();
    public Geometry GetGeometry();
    public SurrealRange GetRange();
    public RecordId GetRecordId();
    public ImmutableArray<SurrealValue> GetArray();
    public ImmutableDictionary<string, SurrealValue> GetObject();

    public bool Equals(SurrealValue other);
    public static bool operator ==(SurrealValue left, SurrealValue right);
    public static bool operator !=(SurrealValue left, SurrealValue right);

    // Implicit conversions: bool, long, double, decimal, string, DateTimeOffset,
    // Guid, SurrealDuration, Table, RecordId.
    public override string ToString();
}

An immutable tagged union over every SurrealDB value kind — the document model used for schemaless data and as the materialization intermediate (comparable in role to System.Text.Json.JsonElement). SurrealValueKind enumerates the sixteen kinds: None = 0, Null = 1, Boolean, Integer, Float, Decimal, String, Duration, DateTime, Uuid, Bytes, RecordId, Table, Array, Object, Range, Geometry.

None vs Null — this distinction is load-bearing, not cosmetic. SurrealValue.None (SurrealValueKind.None, value 0) is also default(SurrealValue) and represents an absent field — SurrealDB's NONE. SurrealValue.Null represents an explicit null value — SurrealDB's NULL. The server treats these as genuinely different, non-interchangeable comparisons: a record created with a field set to NONE omits that key from SELECT * FROM ... entirely, WHERE field = NULL does not match an absent field, and only WHERE field = NONE (or IS NONE) does. This was live-verified against a running SurrealDB 3.1.4 server and is documented as a real incident in the repository's LESSONS-LEARNED.md (2026-07-11): the reflection-based type mapper has always mapped CLR null to SurrealValue.None on the storage path (a null-valued member serializes as an absent field), and the LINQ layer was fixed to route every literal — null included — through that same mapper, so a Where(p => p.Email == null) predicate and a stored null member now agree on SurrealValue.None by construction. Any new code that needs to compare against SurrealDB's NULL must use SurrealValue.Null explicitly; CLR null means NONE.

Each Get* accessor throws InvalidOperationException when Kind doesn't match (message: "The value holds a {Kind}, not a {requested}."). Geometry and SurrealRange intentionally have no implicit conversion — both are reference types where an implicit conversion would invite accidental-boxing-style confusion at call sites — use From(...) explicitly. Byte buffers are likewise explicit-only (From(ReadOnlyMemory<byte>)) to avoid ambiguity against other implicit numeric/array conversions; the input is copied. ToString() returns a short diagnostic description (e.g. "NONE", "true", "[3 items]", "{2 fields}", "<16 bytes>"), not a SurrealQL literal — literal emission belongs to the AST layer (Palm.SurrealDB.Net.SurrealQL).

SurrealDuration
public readonly record struct SurrealDuration : ISpanParsable<SurrealDuration>, IComparable<SurrealDuration>
{
    public static SurrealDuration Zero { get; }
    public ulong Seconds { get; }
    public uint Nanoseconds { get; }
    public SurrealDuration(ulong seconds, uint nanoseconds);
    public static SurrealDuration Parse(string s);
    public static SurrealDuration Parse(string s, IFormatProvider? provider);
    public static SurrealDuration Parse(ReadOnlySpan<char> s, IFormatProvider? provider);
    public static bool TryParse(string? s, IFormatProvider? provider, out SurrealDuration result);
    public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out SurrealDuration result);
    public static SurrealDuration FromTimeSpan(TimeSpan value);
    public TimeSpan ToTimeSpan();
    public int CompareTo(SurrealDuration other);
    public override string ToString();
    public static bool operator <(SurrealDuration left, SurrealDuration right);
    public static bool operator >(SurrealDuration left, SurrealDuration right);
    public static bool operator <=(SurrealDuration left, SurrealDuration right);
    public static bool operator >=(SurrealDuration left, SurrealDuration right);
}

A non-negative SurrealDB duration with nanosecond precision. SurrealDB durations exceed TimeSpan in both range and precision: whole seconds are a 64-bit unsigned count and the fractional part is nanoseconds (a TimeSpan tick is 100 ns). The text form concatenates <number><unit> tokens using units y (365 days), w, d, h, m, s, ms, us/µs, and ns, e.g. "1h30m".

  • SurrealDuration(ulong, uint) throws ArgumentOutOfRangeException when nanoseconds >= 1_000_000_000.
  • Parse throws FormatException for empty input, an unrecognized unit, or a missing number, and OverflowException if the accumulated value overflows (arithmetic is checked).
  • FromTimeSpan throws ArgumentOutOfRangeException for a negative TimeSpan.
  • ToTimeSpan throws OverflowException if the duration exceeds TimeSpan.MaxValue, truncating sub-tick nanoseconds otherwise.
  • ToString formats canonical unit form (e.g. "1m30s"), or "0ns" for the zero duration.
SurrealRange / RangeBound
public readonly record struct RangeBound(SurrealValue Value, bool IsInclusive);

public sealed record SurrealRange(RangeBound? Start, RangeBound? End);

A SurrealDB range value. Either bound may be null (unbounded); each present bound carries its own IsInclusive flag, so 1..=10, 1<..10, ..10, and similar SurrealQL range forms are all representable.

Geometry (Geometry, GeoPoint, and the seven concrete shapes)
public readonly record struct GeoPoint(double Longitude, double Latitude);

public abstract class Geometry : IEquatable<Geometry>
{
    public abstract bool Equals(Geometry? other);
    public abstract override int GetHashCode();
}

public sealed class GeometryPoint : Geometry
{
    public GeometryPoint(GeoPoint coordinate);
    public GeoPoint Coordinate { get; }
}

public sealed class GeometryLine : Geometry
{
    public GeometryLine(IEnumerable<GeoPoint> points);   // >= 2 points required
    public ImmutableArray<GeoPoint> Points { get; }
    public bool IsClosed { get; }                         // first point == last point
}

public sealed class GeometryPolygon : Geometry
{
    public GeometryPolygon(IEnumerable<GeometryLine> rings);  // >= 1 ring; each closed, >= 4 points
    public ImmutableArray<GeometryLine> Rings { get; }         // Rings[0] is the exterior ring
}

public sealed class GeometryMultiPoint : Geometry
{
    public GeometryMultiPoint(IEnumerable<GeoPoint> points);   // may be empty
    public ImmutableArray<GeoPoint> Points { get; }
}

public sealed class GeometryMultiLine : Geometry
{
    public GeometryMultiLine(IEnumerable<GeometryLine> lines); // may be empty
    public ImmutableArray<GeometryLine> Lines { get; }
}

public sealed class GeometryMultiPolygon : Geometry
{
    public GeometryMultiPolygon(IEnumerable<GeometryPolygon> polygons); // may be empty
    public ImmutableArray<GeometryPolygon> Polygons { get; }
}

public sealed class GeometryCollection : Geometry
{
    public GeometryCollection(IEnumerable<Geometry> geometries); // may be empty
    public ImmutableArray<Geometry> Geometries { get; }
}

GeoPoint is (Longitude, Latitude) — GeoJSON ordering (x, y), not (lat, lng). Geometry is the abstract base with GeoJSON semantics; all seven concrete shapes are immutable, value-equatable, and constructed by copying their input sequence into an ImmutableArray.

  • GeometryLine throws ArgumentException when fewer than two points are supplied.
  • GeometryPolygon throws ArgumentException when no rings are supplied, or when any ring is open (first point != last point) or has fewer than four points.
  • GeometryMultiPoint, GeometryMultiLine, GeometryMultiPolygon, and GeometryCollection all accept empty input.
  • All constructors throw ArgumentNullException for a null sequence.

Connection & Options

SurrealDbOptions / ReconnectOptions
public sealed record ReconnectOptions
{
    public bool Enabled { get; init; } = true;
    public TimeSpan InitialDelay { get; init; } = TimeSpan.FromMilliseconds(200);
    public TimeSpan MaxDelay { get; init; } = TimeSpan.FromSeconds(30);
    public int? MaxAttempts { get; init; }
    public double JitterFactor { get; init; } = 0.25;
}

public sealed record SurrealDbOptions
{
    public required Uri Endpoint { get; init; }
    public string? Namespace { get; init; }
    public string? Database { get; init; }
    public SurrealCredentials? Credentials { get; init; }
    public TimeSpan ConnectTimeout { get; init; } = TimeSpan.FromSeconds(5);
    public TimeSpan RequestTimeout { get; init; } = TimeSpan.FromSeconds(30);
    public ReconnectOptions Reconnect { get; init; } = new();
    public int LiveQueryChannelCapacity { get; init; } = 1024;
    public void Validate();
}

ReconnectOptions is the exponential-backoff-with-jitter policy for the WebSocket transport. SurrealDbOptions is the top-level connection configuration passed to a client.

Validate() throws SurrealDbConfigurationException when:

  • Endpoint.Scheme is not one of ws, wss, http, https.
  • Database is set but Namespace is not (a database requires a namespace).
  • ConnectTimeout or RequestTimeout is not positive.
  • LiveQueryChannelCapacity < 1.
  • Reconnect.JitterFactor is outside [0, 1].
  • Reconnect.InitialDelay is not positive, or Reconnect.MaxDelay < Reconnect.InitialDelay.
  • Reconnect.MaxAttempts is set and < 1.
ConnectionState
public enum ConnectionState
{
    Disconnected = 0,
    Connecting = 1,
    Connected = 2,
    Reconnecting = 3,
}

The lifecycle state exposed by ISurrealConnection.State.

Credentials

public abstract record SurrealCredentials;

public sealed record RootCredentials(string Username, string Password) : SurrealCredentials;

public sealed record NamespaceCredentials(string Namespace, string Username, string Password) : SurrealCredentials;

public sealed record DatabaseCredentials(string Namespace, string Database, string Username, string Password) : SurrealCredentials;

public sealed record AccessCredentials(
    string Namespace,
    string Database,
    string AccessMethod,
    IReadOnlyDictionary<string, SurrealValue>? Parameters = null) : SurrealCredentials;

public sealed record TokenCredentials(string Token) : SurrealCredentials;

SurrealCredentials is the closed base type for every credential shape accepted by SurrealDB sign-in; its constructor is private protected, so only the five sealed records in this file can derive from it. Each subtype maps to one SurrealDB authentication level:

  • RootCredentials — root user/password.
  • NamespaceCredentials — namespace-scoped user/password.
  • DatabaseCredentials — database-scoped user/password.
  • AccessCredentials — record-access (DEFINE ACCESS) sign-in, with optional extra sign-in variables via Parameters. Passed to ISurrealClient.SignUpAsync, not SignInAsync.
  • TokenCredentials — an existing JWT, used with ISurrealClient.AuthenticateAsync-style flows.

Client Contracts

ISurrealClient
public interface ISurrealClient : IAsyncDisposable
{
    ISurrealTypeMapper TypeMapper { get; }

    Task ConnectAsync(CancellationToken cancellationToken = default);
    Task UseAsync(string @namespace, string? database = null, CancellationToken cancellationToken = default);
    Task<string> SignInAsync(SurrealCredentials credentials, CancellationToken cancellationToken = default);
    Task<string> SignUpAsync(AccessCredentials credentials, CancellationToken cancellationToken = default);
    Task AuthenticateAsync(string token, CancellationToken cancellationToken = default);
    Task InvalidateAsync(CancellationToken cancellationToken = default);
    Task LetAsync(string name, SurrealValue value, CancellationToken cancellationToken = default);
    Task UnsetAsync(string name, CancellationToken cancellationToken = default);

    Task<T> CreateAsync<T>(Table table, T content, CancellationToken cancellationToken = default);
    Task<T> CreateAsync<T>(RecordId id, T content, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<T>> InsertAsync<T>(Table table, IEnumerable<T> contents, CancellationToken cancellationToken = default);
    Task<IReadOnlyList<T>> SelectAsync<T>(Table table, CancellationToken cancellationToken = default);
    Task<T?> SelectAsync<T>(RecordId id, CancellationToken cancellationToken = default);
    Task<T?> UpdateAsync<T>(RecordId id, T content, CancellationToken cancellationToken = default);
    Task<T?> UpsertAsync<T>(RecordId id, T content, CancellationToken cancellationToken = default);
    Task<T?> MergeAsync<T>(RecordId id, SurrealValue merge, CancellationToken cancellationToken = default);
    Task<T?> PatchAsync<T>(RecordId id, IReadOnlyList<SurrealPatchOperation> operations, CancellationToken cancellationToken = default);
    Task<T?> DeleteAsync<T>(RecordId id, CancellationToken cancellationToken = default);
    Task DeleteAsync(Table table, CancellationToken cancellationToken = default);

    Task<SurrealQueryResponse> QueryAsync(
        string surql,
        IReadOnlyDictionary<string, SurrealValue>? variables = null,
        CancellationToken cancellationToken = default);

    IAsyncEnumerable<LiveQueryChange<T>> LiveAsync<T>(Table table, CancellationToken cancellationToken = default);

    Task<SurrealValue> RunAsync(
        string function,
        IReadOnlyList<SurrealValue>? arguments = null,
        CancellationToken cancellationToken = default);

    Task<string> VersionAsync(CancellationToken cancellationToken = default);
}

The full SurrealDB client surface — connection lifecycle, session management, typed CRUD, raw parameterized queries, live queries, server-side function calls, and version discovery. Implemented by SurrealDbClient in Palm.SurrealDB.Net. Notable behavior, per the XML docs on the interface itself:

  • SignInAsync throws SurrealDbAuthenticationException on failure.
  • SelectAsync<T>(RecordId, ...), UpdateAsync, UpsertAsync, MergeAsync, PatchAsync, and DeleteAsync<T>(RecordId, ...) all return null when the target record does not exist, rather than throwing.
  • QueryAsync's surql parameter takes $name-style parameters via variables; never build query text by string interpolation — see the Palm.SurrealDB.Net.SurrealQL AST/emitter for the only sanctioned way to build query text.
  • LiveAsync<T> throws SurrealDbCapabilityException when the active transport does not support live queries; cancelling the token sends a kill for the live query.
ISurrealConnection
public interface ISurrealConnection : IAsyncDisposable
{
    ConnectionState State { get; }
    bool SupportsLiveQueries { get; }

    Task ConnectAsync(CancellationToken cancellationToken = default);

    Task<SurrealValue> SendAsync(
        string method,
        IReadOnlyList<SurrealValue> parameters,
        CancellationToken cancellationToken = default);

    IAsyncEnumerable<LiveNotification> SubscribeAsync(
        Guid liveQueryId,
        CancellationToken cancellationToken = default);
}

A lower-level transport abstraction beneath ISurrealClient — one RPC-capable connection. Implemented by the WebSocket/HTTP transports in Palm.SurrealDB.Net.Protocol. SendAsync throws SurrealDbRpcException when the server answers with an error object, and SurrealDbConnectionException when the transport itself fails. SubscribeAsync throws SurrealDbCapabilityException when SupportsLiveQueries is false.

ISurrealTypeMapper / ISurrealSerializer
public interface ISurrealTypeMapper
{
    SurrealNamingPolicy NamingPolicy { get; }
    SurrealValue ToSurrealValue<T>(T? value);
    T? FromSurrealValue<T>(SurrealValue value);
}

public interface ISurrealSerializer
{
    void Serialize(IBufferWriter<byte> writer, SurrealValue value);
    SurrealValue Deserialize(ReadOnlySequence<byte> data);
}

ISurrealTypeMapper maps between CLR types and SurrealValue — the single source of truth for property↔field name mapping (see SurrealNamingPolicy below). Implementations may be reflection-based (the default) or source-generated for AOT; the contract is identical either way. The interface methods are deliberately unannotated for trimming/AOT: source-generated mappers are reflection-free, and the reflection-based default carries [RequiresDynamicCode]/[RequiresUnreferencedCode] on its own constructor instead (the IJsonTypeInfoResolver/DefaultJsonTypeInfoResolver shape). ISurrealSerializer encodes/decodes SurrealValue payloads on the wire (CBOR); implemented by Palm.SurrealDB.Net.Serialization.

Attributes

SurrealTableAttribute
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
public sealed class SurrealTableAttribute : Attribute
{
    public SurrealTableAttribute(string name);
    public string Name { get; }
}

Declares the SurrealDB table name for an entity type. When absent, the active SurrealNamingPolicy applied to the CLR type name is used instead. The constructor throws ArgumentException for a null, empty, or whitespace-only name.

SurrealFieldAttribute
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = true)]
public sealed class SurrealFieldAttribute : Attribute
{
    public SurrealFieldAttribute();
    public SurrealFieldAttribute(string name);
    public string? Name { get; }
    public bool Ignore { get; init; }
}

Controls how a property or field maps to a SurrealDB object field. With no explicit name, the active naming policy applies to the CLR member name. Set Ignore = true to exclude a member from serialization entirely.

SurrealNamingPolicy / SurrealNamingPolicyExtensions
public enum SurrealNamingPolicy
{
    AsIs = 0,
    CamelCase = 1,
    SnakeCase = 2,
}

public static class SurrealNamingPolicyExtensions
{
    public static string Apply(this SurrealNamingPolicy policy, string name);
}

The naming strategy for translating CLR member names to SurrealDB field names when no explicit [SurrealField(name)] is given. AsIs uses the CLR name unchanged; CamelCase lowercases the first character (FirstName → firstName); SnakeCase inserts underscores at case boundaries and lowercases (FirstName → first_name). Apply throws ArgumentNullException for a null name.

Query Responses

SurrealQueryResponse / SurrealStatementResult
public sealed record SurrealStatementResult(bool IsSuccess, SurrealValue Value, string? Error, TimeSpan Duration);

public sealed class SurrealQueryResponse
{
    public SurrealQueryResponse(IReadOnlyList<SurrealStatementResult> statements, ISurrealTypeMapper mapper);
    public IReadOnlyList<SurrealStatementResult> Statements { get; }
    public IReadOnlyList<T?> GetResults<T>(int statement = 0);
    public T? GetFirstOrDefault<T>(int statement = 0);
}

SurrealStatementResult is the outcome of one statement in a multi-statement query; Value is SurrealValue.None on failure. SurrealQueryResponse wraps the per-statement results returned by ISurrealClient.QueryAsync and materializes them through the injected ISurrealTypeMapper.

  • GetResults<T>(statement) maps array results element-wise, wraps a single (non-array, non-NONE/NULL) value as a one-element list, and maps NONE/NULL to an empty list. Throws ArgumentOutOfRangeException for an out-of-range statement index, and SurrealDbQueryException (with StatementIndex and ServerMessage set) when the target statement failed on the server.
  • GetFirstOrDefault<T>(statement) returns the first materialized result, or default when the result list is empty.
  • Both materialization methods delegate to the injected ISurrealTypeMapper and are unannotated for trimming/AOT: with a source-generated mapper the whole path is reflection-free, and the reflection-based default mapper declares its unsafety on its own constructor.

Patch Operations

SurrealPatchOperation / PatchOperationType
public enum PatchOperationType
{
    Add = 0,
    Remove = 1,
    Replace = 2,
    Copy = 3,
    Move = 4,
    Test = 5,
}

public sealed record SurrealPatchOperation
{
    public PatchOperationType Type { get; }
    public string Path { get; }
    public SurrealValue Value { get; }   // SurrealValue.None when not applicable
    public string? From { get; }

    public static SurrealPatchOperation Add(string path, SurrealValue value);
    public static SurrealPatchOperation Remove(string path);
    public static SurrealPatchOperation Replace(string path, SurrealValue value);
    public static SurrealPatchOperation Copy(string from, string path);
    public static SurrealPatchOperation Move(string from, string path);
    public static SurrealPatchOperation Test(string path, SurrealValue value);
}

An RFC 6902 JSON Patch operation, consumed by ISurrealClient.PatchAsync. There is no public constructor — operations are built exclusively through the six validating factory methods, each mapping to one JSON Patch verb. Every path (and from, for Copy/Move) is validated to be non-null and to start with /; violating either throws ArgumentNullException or ArgumentException ("A JSON Pointer path must start with '/'.") from the private constructor.

Live Queries

LiveAction
public enum LiveAction
{
    Create = 0,
    Update = 1,
    Delete = 2,
    Killed = 3,
}

The kind of change reported by a live query. Killed signals that the stream ends after this notification.

LiveNotification
public readonly record struct LiveNotification(Guid QueryId, LiveAction Action, SurrealValue Record, SurrealValue Result);

A raw live-query notification as delivered by the transport (ISurrealConnection.SubscribeAsync). Record is the affected record's id as a SurrealValueKind.RecordId value, and Result is the affected record (or patch payload); both are SurrealValue.None for LiveAction.Killed.

LiveQueryChange<T>
public sealed record LiveQueryChange<T>(LiveAction Action, RecordId? Id, T? Record);

A typed live-query change, produced by ISurrealClient.LiveAsync<T> from the raw LiveNotification stream. Id and Record are null for LiveAction.Killed; for Delete, Record carries the full deleted record.

Exceptions

All exceptions derive from SurrealDbException, itself derived from System.Exception, and each follows the standard three-constructor shape ((), (string message), (string message, Exception innerException)) plus any type-specific properties noted below.

public class SurrealDbException : Exception;

Base type for every exception this ecosystem throws.

public sealed class SurrealDbConfigurationException : SurrealDbException;

Thrown when SurrealDbOptions are invalid — see SurrealDbOptions.Validate() above for every condition that triggers it.

public sealed class SurrealDbConnectionException : SurrealDbException
{
    public bool IsRetriable { get; init; }
}

Thrown when the connection to the server fails or is lost. IsRetriable indicates whether retrying the operation may succeed (e.g. after a reconnect).

public sealed class SurrealDbAuthenticationException : SurrealDbException;

Thrown when authentication or authorization fails (e.g. from ISurrealClient.SignInAsync).

public sealed class SurrealDbRpcException : SurrealDbException
{
    public long ErrorCode { get; init; }
    public SurrealValue Details { get; init; }   // SurrealValue.None when absent
    public SurrealValue Cause { get; init; }     // SurrealValue.None when absent
}

Thrown when the server answers an RPC request with an error object (see ISurrealConnection.SendAsync).

public sealed class SurrealDbQueryException : SurrealDbException
{
    public int StatementIndex { get; init; }
    public string? ServerMessage { get; init; }
}

Thrown when a statement inside a multi-statement query response failed on the server (see SurrealQueryResponse.GetResults<T>).

public sealed class SurrealDbSerializationException : SurrealDbException;

Thrown when a value cannot be serialized or deserialized.

public sealed class SurrealDbCapabilityException : SurrealDbException;

Thrown when the active transport does not support the requested operation (e.g. live queries over an HTTP-only transport — see ISurrealClient.LiveAsync and ISurrealConnection.SubscribeAsync).

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.
  • net10.0

    • No dependencies.

NuGet packages (16)

Showing the top 5 NuGet packages that depend on Palm.SurrealDB.Net.Abstractions:

Package Downloads
Palm.SurrealDB.Net.Serialization

CBOR serialization engine and reflection-based type mapper for the Palm.SurrealDB .NET ecosystem.

Palm.SurrealDB.Net.Protocol

WebSocket and HTTP RPC transports for the Palm.SurrealDB .NET ecosystem.

Palm.SurrealDB.Net.SurrealQL

SurrealQL abstract syntax tree and emitter for the Palm.SurrealDB .NET ecosystem.

Palm.SurrealDB.Net

SurrealDB client for .NET — the Palm.SurrealDB ecosystem's primary package.

Palm.SurrealDB.Net.Diagnostics

ActivitySource, EventSource, DiagnosticListener and ILogger instrumentation for the Palm.SurrealDB.Net client.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.2.1 262 9/5/2026
0.2.0 253 9/3/2026