Installation
Install the TrailBase NuGet package:dotnet add package TrailBase
Install-Package TrailBase
Requirements
- .NET 8.0 or .NET 9.0
- Compatible with Native AOT compilation
Initialization
Basic Client
using TrailBase;
var client = new Client("https://your-server.trailbase.io", null);
Client with Tokens
using TrailBase;
var tokens = new Tokens(
auth_token: "your-auth-token",
refresh_token: "your-refresh-token",
csrf_token: "your-csrf-token"
);
var client = new Client("https://your-server.trailbase.io", tokens);
Authentication
Login
try
{
var tokens = await client.Login("[email protected]", "password");
Console.WriteLine($"Auth token: {tokens.auth_token}");
var user = client.User();
if (user != null)
{
Console.WriteLine($"Logged in as: {user.email}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Login failed: {ex.Message}");
}
Logout
await client.Logout();
Current User
var user = client.User();
if (user != null)
{
Console.WriteLine($"User ID: {user.sub}");
Console.WriteLine($"Email: {user.email}");
}
Access Tokens
var tokens = client.Tokens();
if (tokens != null)
{
// Persist tokens for later use
var json = System.Text.Json.JsonSerializer.Serialize(tokens);
await File.WriteAllTextAsync("tokens.json", json);
}
Refresh Token
await client.RefreshAuthToken();
Record API
Define Your Record Types
using System.Text.Json.Serialization;
public class Post
{
[JsonPropertyName("id")]
public string Id { get; set; } = "";
[JsonPropertyName("title")]
public string Title { get; set; } = "";
[JsonPropertyName("content")]
public string Content { get; set; } = "";
[JsonPropertyName("author_id")]
public string AuthorId { get; set; } = "";
[JsonPropertyName("created_at")]
public long CreatedAt { get; set; }
}
public class NewPost
{
[JsonPropertyName("title")]
public string Title { get; set; } = "";
[JsonPropertyName("content")]
public string Content { get; set; } = "";
}
List Records
var posts = client.Records("posts");
var response = await posts.List<Post>(
limit: 10,
offset: 0,
order: new[] { "-created_at" },
count: true
);
Console.WriteLine($"Records: {response.records.Count}");
Console.WriteLine($"Total count: {response.total_count}");
Console.WriteLine($"Next cursor: {response.cursor}");
Read a Record
// String ID
var post = await posts.Read<Post>("post-id");
// Integer ID
var post = await posts.Read<Post>(123);
// With expanded relationships
var postWithAuthor = await posts.Read<Post>(
"post-id",
expand: new[] { "author" }
);
Console.WriteLine($"Title: {post.Title}");
Create a Record
var newPost = new NewPost
{
Title = "Hello World",
Content = "My first post from C#"
};
var postId = await posts.Create(newPost);
Console.WriteLine($"Created post with ID: {postId}");
Create Multiple Records
var newPosts = new[]
{
new NewPost { Title = "Post 1", Content = "Content 1" },
new NewPost { Title = "Post 2", Content = "Content 2" }
};
var ids = await posts.CreateBulk(newPosts);
Console.WriteLine($"Created {ids.Count} posts");
Update a Record
var updates = new Dictionary<string, object>
{
{ "title", "Updated Title" }
};
await posts.Update("post-id", updates);
Delete a Record
await posts.Delete("post-id");
Filtering
using TrailBase;
// Simple equality filter
var filters = new[]
{
new Filter(column: "author_id", value: userId)
};
var response = await posts.List<Post>(filters: filters);
// With comparison operators
var weekAgo = DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeSeconds();
var recentPosts = await posts.List<Post>(
filters: new[]
{
new Filter(
column: "created_at",
op: CompareOp.GreaterThan,
value: weekAgo.ToString()
)
}
);
// LIKE operator for text search
var searchResults = await posts.List<Post>(
filters: new[]
{
new Filter(
column: "title",
op: CompareOp.Like,
value: "%search%"
)
}
);
// AND composite filter
var filtered = await posts.List<Post>(
filters: new[]
{
new And(new[]
{
new Filter(column: "status", value: "published"),
new Filter(column: "author_id", value: userId)
})
}
);
// OR composite filter
var filtered = await posts.List<Post>(
filters: new[]
{
new Or(new[]
{
new Filter(column: "category", value: "tech"),
new Filter(column: "category", value: "science")
})
}
);
Available Comparison Operators
public enum CompareOp
{
Equal,
NotEqual,
LessThan,
LessThanEqual,
GreaterThan,
GreaterThanEqual,
Like,
Regexp,
StWithin, // Geospatial
StIntersects, // Geospatial
StContains // Geospatial
}
Real-time Subscriptions
Subscribe to record changes using Server-Sent Events:// Subscribe to a single record
await foreach (var @event in posts.Subscribe("post-id"))
{
if (@event.Insert != null)
{
Console.WriteLine($"Record inserted: {@event.Insert}");
}
else if (@event.Update != null)
{
Console.WriteLine($"Record updated: {@event.Update}");
}
else if (@event.Delete != null)
{
Console.WriteLine($"Record deleted: {@event.Delete}");
}
else if (@event.Error != null)
{
Console.WriteLine($"Error: {@event.Error}");
}
}
Error Handling
using System.Net.Http;
try
{
var post = await posts.Read<Post>("post-id");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"HTTP error: {ex.StatusCode}");
Console.WriteLine($"Message: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Advanced Usage
Custom Fetch
var response = await client.Fetch(
"api/custom/endpoint",
HttpMethod.Post,
new StringContent(
System.Text.Json.JsonSerializer.Serialize(new { key = "value" }),
System.Text.Encoding.UTF8,
"application/json"
)
);
var data = await response.Content.ReadAsStringAsync();
Pagination
var pagination = new Pagination
{
Limit = 20,
Offset = 0
};
var response = await posts.List<Post>(pagination: pagination);
// Cursor-based pagination
var nextPage = await posts.List<Post>(
pagination: new Pagination { Cursor = response.cursor }
);
Type Definitions
User
public class User
{
public string sub { get; }
public string email { get; }
public User(string sub, string email)
{
this.sub = sub;
this.email = email;
}
}
Tokens
public class Tokens
{
public string auth_token { get; }
public string? refresh_token { get; }
public string? csrf_token { get; }
public Tokens(string auth_token, string? refresh_token, string? csrf_token)
{
this.auth_token = auth_token;
this.refresh_token = refresh_token;
this.csrf_token = csrf_token;
}
}
ListResponse
public class ListResponse<T>
{
public string? cursor { get; set; }
public int? total_count { get; set; }
public List<T> records { get; set; }
}
Pagination
public class Pagination
{
public string? Cursor { get; set; }
public int? Limit { get; set; }
public int? Offset { get; set; }
}
Best Practices
Use nullable reference types (enabled by default in modern .NET) for better null safety.
Store tokens securely using Windows Credential Manager, Azure Key Vault, or similar secure storage.
The SDK is AOT-compatible and works with Native AOT compilation for improved startup time and reduced memory usage.
Example Application
using TrailBase;
using System.Text.Json.Serialization;
public class Post
{
[JsonPropertyName("id")]
public string Id { get; set; } = "";
[JsonPropertyName("title")]
public string Title { get; set; } = "";
[JsonPropertyName("content")]
public string Content { get; set; } = "";
[JsonPropertyName("published")]
public bool Published { get; set; }
}
class Program
{
static async Task Main(string[] args)
{
// Initialize client
var url = Environment.GetEnvironmentVariable("TRAILBASE_URL")
?? "http://localhost:4000";
var client = new Client(url, null);
// Login
try
{
await client.Login(
Environment.GetEnvironmentVariable("TRAILBASE_EMAIL")!,
Environment.GetEnvironmentVariable("TRAILBASE_PASSWORD")!
);
var user = client.User();
Console.WriteLine($"Logged in as: {user?.email}");
}
catch (Exception ex)
{
Console.WriteLine($"Login failed: {ex.Message}");
return;
}
// List posts
var posts = client.Records("posts");
var response = await posts.List<Post>(
order: new[] { "-created_at" },
limit: 10,
filters: new[]
{
new Filter(column: "published", value: "true")
}
);
Console.WriteLine($"\nFound {response.records.Count} posts:");
foreach (var post in response.records)
{
Console.WriteLine($"- {post.Title}");
}
// Create a new post
var newPost = new Dictionary<string, object>
{
{ "title", "Hello from C#" },
{ "content", "This post was created using the TrailBase C# SDK" },
{ "published", true }
};
var newPostId = await posts.Create(newPost);
Console.WriteLine($"\nCreated new post with ID: {newPostId}");
// Read the post
var post = await posts.Read<Post>(newPostId);
Console.WriteLine($"Post title: {post.Title}");
// Logout
await client.Logout();
Console.WriteLine("\nLogged out");
}
}