Palm.SurrealDB.Net
0.2.1
dotnet add package Palm.SurrealDB.Net --version 0.2.1
NuGet\Install-Package Palm.SurrealDB.Net -Version 0.2.1
<PackageReference Include="Palm.SurrealDB.Net" Version="0.2.1" />
<PackageVersion Include="Palm.SurrealDB.Net" Version="0.2.1" />
<PackageReference Include="Palm.SurrealDB.Net" />
paket add Palm.SurrealDB.Net --version 0.2.1
#r "nuget: Palm.SurrealDB.Net, 0.2.1"
#:package Palm.SurrealDB.Net@0.2.1
#addin nuget:?package=Palm.SurrealDB.Net&version=0.2.1
#tool nuget:?package=Palm.SurrealDB.Net&version=0.2.1
Palm.SurrealDB.Net
The client package of the Palm.SurrealDB ecosystem — the primary,
consumer-facing package most applications install. It ships SurrealDbClient, a single
sealed implementation of ISurrealClient that binds SurrealDbOptions to a WebSocket or
HTTP transport, a CBOR serializer, and a reflection-based type mapper, plus an
AddSurrealDb extension for Microsoft.Extensions.DependencyInjection. Everything else in
the ecosystem — the SurrealQL AST/emitter, the LINQ provider, the F# DSL — sits on top of, or
alongside, what this package exposes. If you only take one dependency to talk to SurrealDB
from .NET, this is it.
Targets net10.0 only. See the root README for the full ecosystem
map, build/test commands, and project status.
Installation
dotnet add package Palm.SurrealDB.Net
This project is packable and ships as the Palm.SurrealDB.Net 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/Palm.SurrealDB.Net.csproj" />
</ItemGroup>
Contents
- Quick start
SurrealDbClient- Dependency injection —
AddSurrealDb SurrealDbOptionssummary- Error handling
- Worked examples
- Related projects
Quick start
using Palm.SurrealDB.Net;
await using var client = new SurrealDbClient(new SurrealDbOptions
{
Endpoint = new Uri("ws://127.0.0.1:8000/rpc"),
Namespace = "test",
Database = "test",
Credentials = new RootCredentials("root", "root"),
});
await client.ConnectAsync();
var ada = await client.CreateAsync(new Table("person"), new Person("Ada", 30));
SurrealDbClient
SurrealDbClient (Palm.SurrealDB.Net.SurrealDbClient) is sealed and implements the full
ISurrealClient surface (defined in Palm.SurrealDB.Net.Abstractions; summarized here member
by member since callers of this package need the full contract without hopping projects).
Sessions initialize lazily on first use — connect, sign in, USE namespace/database — and are
restored automatically after a WebSocket reconnect.
Constructor
public SurrealDbClient(
SurrealDbOptions options,
ISurrealConnection? connection = null,
ISurrealTypeMapper? mapper = null,
ISurrealSerializer? serializer = null)
| Parameter | Default | Notes |
|---|---|---|
options |
(required) | Validated eagerly via options.Validate(); throws SurrealDbConfigurationException on invalid options (see SurrealDbOptions summary). |
connection |
null |
When omitted, the transport is chosen from options.Endpoint.Scheme: ws/wss → WebSocketConnection, http/https → HttpConnection. The client owns and disposes a transport it creates; an externally supplied connection is not disposed by DisposeAsync. Pass a fake/mock ISurrealConnection for unit testing without a live server. |
mapper |
new ReflectionSurrealTypeMapper() |
Implements ISurrealTypeMapper (from Palm.SurrealDB.Net.Serialization); override to plug in a source-generated/AOT-safe mapper. Exposed afterward via TypeMapper. |
serializer |
CBOR (Palm.SurrealDB.Net.Protocol's default) |
Only used when the client creates its own transport (i.e. connection is null); ignored otherwise. |
Connection lifecycle
| Member | Signature | Behavior |
|---|---|---|
ConnectAsync |
Task ConnectAsync(CancellationToken cancellationToken = default) |
Establishes the connection and runs the initial session sequence (authenticate/sign in, then USE). Idempotent — subsequent calls, and every other client method, share the same lazy one-time initialization guarded by an internal semaphore. Throws ObjectDisposedException if the client was disposed. |
UseAsync |
Task UseAsync(string @namespace, string? database = null, CancellationToken cancellationToken = default) |
Selects namespace and, optionally, database. Updates the session's remembered namespace/database so a later reconnect restores it. |
SignInAsync |
Task<string> SignInAsync(SurrealCredentials credentials, CancellationToken cancellationToken = default) |
Signs in with RootCredentials, NamespaceCredentials, DatabaseCredentials, AccessCredentials, or TokenCredentials and returns the session token (empty string if the server didn't return one). TokenCredentials short-circuits to AuthenticateAsync. Remembers the credentials for reconnect. |
SignUpAsync |
Task<string> SignUpAsync(AccessCredentials credentials, CancellationToken cancellationToken = default) |
Signs up a new record-access identity via a DEFINE ACCESS method and returns its token. |
AuthenticateAsync |
Task AuthenticateAsync(string token, CancellationToken cancellationToken = default) |
Authenticates the session with an existing JWT. Remembers the token for reconnect (clears any remembered credentials). |
InvalidateAsync |
Task InvalidateAsync(CancellationToken cancellationToken = default) |
Invalidates the session's authentication; clears remembered token/credentials so a reconnect won't re-authenticate. |
DisposeAsync |
ValueTask DisposeAsync() |
Idempotent. Disposes the owned transport (if the client created one) and the internal session semaphore. Does not dispose an externally supplied connection. |
Reconnect behavior: when the transport is WebSocketConnection, the client wires its
SessionRestore callback to re-run the same authenticate/sign-in + USE sequence
automatically after a reconnect, using whichever of token or credentials was last set and the
last-selected namespace/database.
CRUD
All CRUD methods delegate to the server's respective RPC method (create, select,
update, upsert, merge, patch, delete, insert) via the type mapper for
serialization/deserialization. RecordId-targeted single-record calls return T? (or throw,
for CreateAsync); Table-targeted calls return IReadOnlyList<T> or Task for
bulk/no-content operations.
| Member | Signature | Behavior |
|---|---|---|
CreateAsync<T> (table) |
Task<T> CreateAsync<T>(Table table, T content, CancellationToken cancellationToken = default) |
Creates a record with a server-generated id. Throws SurrealDbSerializationException if the server returns no created record. |
CreateAsync<T> (id) |
Task<T> CreateAsync<T>(RecordId id, T content, CancellationToken cancellationToken = default) |
Creates a record with the given id. Same SurrealDbSerializationException guarantee. |
InsertAsync<T> |
Task<IReadOnlyList<T>> InsertAsync<T>(Table table, IEnumerable<T> contents, CancellationToken cancellationToken = default) |
Bulk-inserts records via the insert RPC method and returns them as inserted. |
SelectAsync<T> (table) |
Task<IReadOnlyList<T>> SelectAsync<T>(Table table, CancellationToken cancellationToken = default) |
Selects every record of a table. |
SelectAsync<T> (id) |
Task<T?> SelectAsync<T>(RecordId id, CancellationToken cancellationToken = default) |
Selects one record; null if it doesn't exist. |
UpdateAsync<T> |
Task<T?> UpdateAsync<T>(RecordId id, T content, CancellationToken cancellationToken = default) |
Replaces a record's content wholesale; null if absent. CLR null properties serialize as explicit NONE — they are set to NONE on the record, not omitted. |
UpsertAsync<T> |
Task<T?> UpsertAsync<T>(RecordId id, T content, CancellationToken cancellationToken = default) |
Creates the record if absent, else replaces it; returns the resulting record. |
MergeAsync<T> |
Task<T?> MergeAsync<T>(RecordId id, SurrealValue merge, CancellationToken cancellationToken = default) |
Merges fields from a SurrealValue object into the record (partial update). Merge fields whose value is NONE are set to NONE, not skipped. null if the record is absent. |
PatchAsync<T> |
Task<T?> PatchAsync<T>(RecordId id, IReadOnlyList<SurrealPatchOperation> operations, CancellationToken cancellationToken = default) |
Applies RFC 6902 JSON Patch operations built via SurrealPatchOperation.Add/Remove/Replace/Copy/Move/Test. null if the record is absent. |
DeleteAsync<T> (id) |
Task<T?> DeleteAsync<T>(RecordId id, CancellationToken cancellationToken = default) |
Deletes one record and returns it (the deleted content), or null if it didn't exist. |
DeleteAsync (table) |
Task DeleteAsync(Table table, CancellationToken cancellationToken = default) |
Deletes every record of a table. |
Table and RecordId are value types from Palm.SurrealDB.Net.Abstractions; construct a
Table from a raw name (new Table("person") or the implicit string conversion) and a
RecordId from a Table and a RecordIdKey (table.Id(RecordIdKey.From("ada")), or
RecordId.Parse("person:ada")). Full reference:
../Palm.SurrealDB.Net.Abstractions/README.md.
Raw queries — QueryAsync
public Task<SurrealQueryResponse> QueryAsync(
string surql,
IReadOnlyDictionary<string, SurrealValue>? variables = null,
CancellationToken cancellationToken = default)
Executes raw, parameterized SurrealQL text via the query RPC method and returns a
SurrealQueryResponse — one SurrealStatementResult per statement in surql, in order.
variables supplies $name parameter bindings; always use $name parameters, never string
interpolation, to build dynamic queries. surql must be non-empty
(ArgumentException.ThrowIfNullOrEmpty).
SurrealQueryResponse exposes:
IReadOnlyList<SurrealStatementResult> Statements— each withIsSuccess,Value(SurrealValue),Error(server message on failure), andDuration.IReadOnlyList<T?> GetResults<T>(int statement = 0)— materializes a statement's results through the client's type mapper: an array result maps element-wise, a single value maps to a one-element list,NONE/NULLmap to an empty list. ThrowsSurrealDbQueryException(withStatementIndex/ServerMessage) if that statement failed on the server, andArgumentOutOfRangeExceptionfor an out-of-rangestatementindex.T? GetFirstOrDefault<T>(int statement = 0)— the first materialized result, ordefault.
For building queries programmatically instead of hand-writing SurrealQL text, use the AST and
emitter in Palm.SurrealDB.Net.SurrealQL — never concatenate query strings.
LINQ — Query<T>()
Palm.SurrealDB.Net.Linq adds ISurrealClient.Query<T>(), an IQueryable<T> entry point
that translates LINQ expression trees to SurrealQL (strict-throw — it never falls back to
client-side evaluation of anything it can't translate):
using Palm.SurrealDB.Net.Linq;
var adults = await client.Query<Person>()
.Where(p => p.Age >= 18)
.OrderBy(p => p.Name)
.Take(10)
.ToListAsync();
Query<T>() itself is defined in Palm.SurrealDB.Net.Linq, not this package — add a
reference to that project to use it. Full LINQ operator reference, translation rules, and the
strict-throw contract:
../Palm.SurrealDB.Net.Linq/README.md.
Live queries — LiveAsync<T>
public IAsyncEnumerable<LiveQueryChange<T>> LiveAsync<T>(
Table table, CancellationToken cancellationToken = default)
Starts a live query on table via the live RPC method and streams typed
LiveQueryChange<T> values (Action, Id, Record) until the enumeration is cancelled or a
LiveAction.Killed notification arrives. Requires the WebSocket transport — throws
SurrealDbCapabilityException if the active connection doesn't support live queries (e.g. an
HttpConnection). On normal disposal of the enumerator (not already killed, client not
disposed), the client best-effort sends kill for the live query id; a SurrealDbException
during that best-effort kill is swallowed since the live query dies with the session anyway.
await foreach (var change in client.LiveAsync<Person>(new Table("person"), cts.Token))
{
switch (change.Action)
{
case LiveAction.Create:
case LiveAction.Update:
Console.WriteLine($"{change.Action}: {change.Record}");
break;
case LiveAction.Delete:
Console.WriteLine($"Deleted: {change.Id}");
break;
case LiveAction.Killed:
Console.WriteLine("Live query killed.");
break;
}
}
LiveQueryChange<T>.Record is null for LiveAction.Killed; for Delete it carries the
full deleted record, materialized via the client's type mapper.
Session variables and functions
| Member | Signature | Behavior |
|---|---|---|
LetAsync |
Task LetAsync(string name, SurrealValue value, CancellationToken cancellationToken = default) |
Sets a session variable usable as $name in subsequent queries. |
UnsetAsync |
Task UnsetAsync(string name, CancellationToken cancellationToken = default) |
Removes a session variable. |
RunAsync |
Task<SurrealValue> RunAsync(string function, IReadOnlyList<SurrealValue>? arguments = null, CancellationToken cancellationToken = default) |
Runs a server-side function (e.g. "fn::my_func") with positional arguments and returns its result. |
VersionAsync |
Task<string> VersionAsync(CancellationToken cancellationToken = default) |
Returns the server's version string. |
TypeMapper
public ISurrealTypeMapper TypeMapper { get; }
Exposes the ISurrealTypeMapper the client was constructed with (or the default
ReflectionSurrealTypeMapper) — the same instance used internally to serialize/deserialize
CRUD payloads and query results. Useful for calling ToSurrealValue/FromSurrealValue
directly, e.g. when building a MergeAsync payload or interpreting a raw SurrealValue.
Dependency injection — AddSurrealDb
SurrealDbServiceCollectionExtensions (static class) adds two AddSurrealDb overloads,
both registering a singleton ISurrealClient via TryAddSingleton (a no-op if
ISurrealClient is already registered):
// Direct options
public static IServiceCollection AddSurrealDb(
this IServiceCollection services, SurrealDbOptions options)
// Factory-based — resolve options from the container (e.g. IConfiguration/IOptions)
public static IServiceCollection AddSurrealDb(
this IServiceCollection services,
Func<IServiceProvider, SurrealDbOptions> optionsFactory)
The direct-options overload calls options.Validate() eagerly, at registration time —
an invalid SurrealDbOptions throws SurrealDbConfigurationException immediately during
ConfigureServices, before the host even starts. The factory-based overload defers
construction (and therefore validation) until the container first resolves ISurrealClient,
since the factory itself may depend on other registered services.
SurrealDbOptions summary
SurrealDbOptions (Palm.SurrealDB.Net.Abstractions) is the primary configuration surface:
Uri Endpoint— required; scheme must bews,wss,http, orhttps.string? Namespace,string? Database— selected after connecting;DatabaserequiresNamespace.SurrealCredentials? Credentials—RootCredentials/NamespaceCredentials/DatabaseCredentials/AccessCredentials/TokenCredentials.TimeSpan ConnectTimeout(default 5s),TimeSpan RequestTimeout(default 30s) — both must be positive.ReconnectOptions Reconnect—Enabled(defaulttrue),InitialDelay(200ms),MaxDelay(30s),MaxAttempts(null= unlimited),JitterFactor(0.25, must be in[0, 1]).int LiveQueryChannelCapacity(default 1024) — bounded capacity of each live-query notification channel; must be at least 1.
Validate() throws SurrealDbConfigurationException for any violation above; both
SurrealDbClient's constructor and the direct-options AddSurrealDb overload call it
eagerly. Full reference (every member, defaults, and validation rule):
../Palm.SurrealDB.Net.Abstractions/README.md.
Error handling
Every exception type below derives from SurrealDbException (itself an Exception) and
lives in Palm.SurrealDB.Net.Abstractions:
| Exception | Thrown by (in this package) |
|---|---|
SurrealDbConfigurationException |
SurrealDbClient's constructor and AddSurrealDb(options) when SurrealDbOptions.Validate() rejects the options (bad endpoint scheme, Database without Namespace, non-positive timeouts, invalid reconnect settings, LiveQueryChannelCapacity < 1). |
SurrealDbConnectionException |
Raised by the underlying transport (Palm.SurrealDB.Net.Protocol) when the connection fails or is lost; propagates through any client method that requires a live session. Carries IsRetriable indicating whether a retry (e.g. after reconnect) may succeed. |
SurrealDbAuthenticationException |
Sign-in/authentication failures, including an unsupported SurrealCredentials subtype passed to a credentials-accepting method. |
SurrealDbRpcException |
The server answers an RPC call with an error object; carries ErrorCode, Details, and Cause. |
SurrealDbQueryException |
A statement inside a QueryAsync response failed server-side, surfaced when calling SurrealQueryResponse.GetResults<T>/GetFirstOrDefault<T>. Carries StatementIndex and ServerMessage. |
SurrealDbSerializationException |
CreateAsync<T> when the server returns no created record; also raised by the type mapper for values that can't be serialized/deserialized. |
SurrealDbCapabilityException |
LiveAsync<T> when the active transport (e.g. HttpConnection) doesn't support live queries. |
ObjectDisposedException |
Any method called after DisposeAsync() has completed. |
ArgumentNullException/ArgumentException (BCL types, not SurrealDbException subtypes) are
thrown for straightforward null/empty argument violations (options, credentials,
operations, contents, @namespace, token, name, function, surql) before any
network call is attempted.
Worked examples
1. Connect and perform typed CRUD
using Palm.SurrealDB.Net;
public sealed record Person(string Name, int Age);
await using var client = new SurrealDbClient(new SurrealDbOptions
{
Endpoint = new Uri("ws://127.0.0.1:8000/rpc"),
Namespace = "test",
Database = "test",
Credentials = new RootCredentials("root", "root"),
});
await client.ConnectAsync();
var table = new Table("person");
var ada = await client.CreateAsync(table, new Person("Ada", 30));
var found = await client.SelectAsync<Person>(new RecordId(table, RecordIdKey.From("ada")));
var updated = await client.MergeAsync<Person>(
new RecordId(table, RecordIdKey.From("ada")),
SurrealValue.FromObject([new("age", SurrealValue.From(31))]));
var everyone = await client.SelectAsync<Person>(table);
var deleted = await client.DeleteAsync<Person>(new RecordId(table, RecordIdKey.From("ada")));
2. Raw QueryAsync with parameters
var response = await client.QueryAsync(
"SELECT * FROM person WHERE age >= $minAge ORDER BY name",
new Dictionary<string, SurrealValue> { ["minAge"] = SurrealValue.From(18) });
var adults = response.GetResults<Person>(); // statement 0
var firstAdult = response.GetFirstOrDefault<Person>();
3. DI registration in an ASP.NET Core / generic host Program.cs
using Palm.SurrealDB.Net;
var builder = WebApplication.CreateBuilder(args);
// Direct options — validated eagerly, at registration time.
builder.Services.AddSurrealDb(new SurrealDbOptions
{
Endpoint = new Uri("ws://127.0.0.1:8000/rpc"),
Namespace = "test",
Database = "test",
Credentials = new RootCredentials("root", "root"),
});
// Or: factory-based, resolving options from configuration.
builder.Services.AddSurrealDb(sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return new SurrealDbOptions
{
Endpoint = new Uri(config["SurrealDb:Endpoint"]!),
Namespace = config["SurrealDb:Namespace"],
Database = config["SurrealDb:Database"],
Credentials = new RootCredentials(
config["SurrealDb:User"]!, config["SurrealDb:Pass"]!),
};
});
var app = builder.Build();
app.MapGet("/people", async (ISurrealClient client, CancellationToken ct) =>
await client.SelectAsync<Person>(new Table("person"), ct));
await app.RunAsync();
Related projects
| Project | Purpose |
|---|---|
Palm.SurrealDB.Net.Abstractions |
Core value types and contracts this package depends on: RecordId, Table, SurrealValue, SurrealDbOptions, ISurrealClient, ISurrealTypeMapper, [SurrealTable]/[SurrealField]. |
Palm.SurrealDB.Net.Serialization |
The CBOR codec for SurrealValue and ReflectionSurrealTypeMapper (ISurrealTypeMapper's default implementation), the single source of truth for property↔field name mapping. |
Palm.SurrealDB.Net.Protocol |
The WebSocket and HTTP RPC transports (WebSocketConnection, HttpConnection) this client selects between based on SurrealDbOptions.Endpoint. |
Palm.SurrealDB.Net.SurrealQL |
The SurrealQL AST, parameterized emitter, parser, and formatter — the only supported path for building query text programmatically. |
Palm.SurrealDB.Net.Linq |
The IQueryable<T> LINQ-to-SurrealQL provider behind client.Query<T>(). |
Palm.SurrealDB.FSharp |
The idiomatic F# DSL layered over the SurrealQL AST. |
| Root README | Ecosystem overview, build/test commands, and phase status. |
| 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
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.10)
- Palm.SurrealDB.Net.Abstractions (>= 0.2.1)
- Palm.SurrealDB.Net.Protocol (>= 0.2.1)
- Palm.SurrealDB.Net.Serialization (>= 0.2.1)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on Palm.SurrealDB.Net:
| Package | Downloads |
|---|---|
|
Palm.SurrealDB.Net.Linq
LINQ provider for SurrealDB — translates expression trees to SurrealQL via the Palm.SurrealDB.Net.SurrealQL AST. |
|
|
Palm.SurrealDB.EntityFrameworkCore
Entity Framework Core provider for SurrealDB, built on the Palm.SurrealDB.Net client and SurrealQL AST. |
|
|
Palm.SurrealDB.Net.AspNetCore
ASP.NET Core integration for SurrealDB — configuration-bound client registration, scoped request context, and bearer-token authentication handler for the Palm.SurrealDB ecosystem. |
|
|
Palm.SurrealDB.Net.Aspire
.NET Aspire client integration component for SurrealDB — AddSurrealDbClient/AddKeyedSurrealDbClient, wiring health checks, tracing, metrics, and logging conventions for worker services. |
|
|
Palm.SurrealDB.Net.Orleans
Microsoft Orleans grain-persistence provider backed by SurrealDB — queryable document grain state with optimistic concurrency, for the Palm.SurrealDB ecosystem. |
GitHub repositories
This package is not used by any popular GitHub repositories.