synor/sdk/csharp/Synor.Sdk/Database/SynorDatabase.cs
Gulshan Yadav a874faef13 feat: complete Phase 3 SDKs for Swift, C, C++, C#, and Ruby
Implements Database, Hosting, and Bridge SDKs for remaining languages:

Swift SDKs:
- SynorDatabase with KV, Document, Vector, TimeSeries stores
- SynorHosting with domain, DNS, deployment, SSL operations
- SynorBridge with lock-mint and burn-unlock cross-chain flows

C SDKs:
- database.h/c - multi-model database client
- hosting.h/c - hosting and domain management
- bridge.h/c - cross-chain asset transfers

C++ SDKs:
- database.hpp - modern C++17 with std::future async
- hosting.hpp - domain and deployment operations
- bridge.hpp - cross-chain bridge with wait operations

C# SDKs:
- SynorDatabase.cs - async/await with inner store classes
- SynorHosting.cs - domain management and analytics
- SynorBridge.cs - cross-chain with BridgeException handling

Ruby SDKs:
- synor_database - Struct-based types with Faraday HTTP
- synor_hosting - domain, DNS, SSL, analytics
- synor_bridge - lock-mint/burn-unlock with retry logic

Phase 3 complete: Database/Hosting/Bridge now available in all 12 languages.
2026-01-27 02:23:07 +05:30

335 lines
12 KiB
C#

using System.Net.Http.Json;
using System.Text.Json;
using System.Web;
namespace Synor.Sdk.Database;
/// <summary>
/// Synor Database SDK client for C#.
///
/// Provides multi-model database with Key-Value, Document, Vector, and Time Series stores.
/// </summary>
/// <example>
/// <code>
/// var db = new SynorDatabase(new DatabaseConfig { ApiKey = "your-api-key" });
///
/// // Key-Value operations
/// await db.Kv.SetAsync("mykey", "myvalue");
/// var value = await db.Kv.GetAsync("mykey");
///
/// // Document operations
/// var id = await db.Documents.CreateAsync("users", new { name = "Alice", age = 30 });
/// var doc = await db.Documents.GetAsync("users", id);
///
/// db.Dispose();
/// </code>
/// </example>
public class SynorDatabase : IDisposable
{
private readonly DatabaseConfig _config;
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonOptions;
private bool _disposed;
public KeyValueStore Kv { get; }
public DocumentStore Documents { get; }
public VectorStore Vectors { get; }
public TimeSeriesStore TimeSeries { get; }
public SynorDatabase(DatabaseConfig config)
{
_config = config;
_httpClient = new HttpClient
{
BaseAddress = new Uri(config.Endpoint),
Timeout = config.Timeout
};
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {config.ApiKey}");
_httpClient.DefaultRequestHeaders.Add("X-SDK-Version", "csharp/0.1.0");
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
PropertyNameCaseInsensitive = true
};
Kv = new KeyValueStore(this);
Documents = new DocumentStore(this);
Vectors = new VectorStore(this);
TimeSeries = new TimeSeriesStore(this);
}
/// <summary>
/// Key-Value store operations.
/// </summary>
public class KeyValueStore
{
private readonly SynorDatabase _db;
internal KeyValueStore(SynorDatabase db) => _db = db;
public async Task<object?> GetAsync(string key, CancellationToken ct = default)
{
var response = await _db.GetAsync<KvGetResponse>($"/kv/{Uri.EscapeDataString(key)}", ct);
return response.Value;
}
public async Task SetAsync(string key, object value, int? ttl = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["key"] = key, ["value"] = value };
if (ttl.HasValue) body["ttl"] = ttl.Value;
await _db.PutAsync<object>($"/kv/{Uri.EscapeDataString(key)}", body, ct);
}
public async Task DeleteAsync(string key, CancellationToken ct = default)
{
await _db.DeleteAsync($"/kv/{Uri.EscapeDataString(key)}", ct);
}
public async Task<List<KeyValue>> ListAsync(string prefix, CancellationToken ct = default)
{
var response = await _db.GetAsync<KvListResponse>($"/kv?prefix={Uri.EscapeDataString(prefix)}", ct);
return response.Items ?? new List<KeyValue>();
}
}
/// <summary>
/// Document store operations.
/// </summary>
public class DocumentStore
{
private readonly SynorDatabase _db;
internal DocumentStore(SynorDatabase db) => _db = db;
public async Task<string> CreateAsync(string collection, object document, CancellationToken ct = default)
{
var response = await _db.PostAsync<CreateDocumentResponse>(
$"/collections/{Uri.EscapeDataString(collection)}/documents", document, ct);
return response.Id;
}
public async Task<Document> GetAsync(string collection, string id, CancellationToken ct = default)
{
return await _db.GetAsync<Document>(
$"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", ct);
}
public async Task UpdateAsync(string collection, string id, object update, CancellationToken ct = default)
{
await _db.PatchAsync<object>(
$"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", update, ct);
}
public async Task DeleteAsync(string collection, string id, CancellationToken ct = default)
{
await _db.DeleteAsync(
$"/collections/{Uri.EscapeDataString(collection)}/documents/{Uri.EscapeDataString(id)}", ct);
}
public async Task<List<Document>> QueryAsync(string collection, DocumentQuery query, CancellationToken ct = default)
{
var response = await _db.PostAsync<DocumentListResponse>(
$"/collections/{Uri.EscapeDataString(collection)}/query", query, ct);
return response.Documents ?? new List<Document>();
}
}
/// <summary>
/// Vector store operations.
/// </summary>
public class VectorStore
{
private readonly SynorDatabase _db;
internal VectorStore(SynorDatabase db) => _db = db;
public async Task UpsertAsync(string collection, IEnumerable<VectorEntry> vectors, CancellationToken ct = default)
{
await _db.PostAsync<object>(
$"/vectors/{Uri.EscapeDataString(collection)}/upsert",
new { vectors = vectors }, ct);
}
public async Task<List<SearchResult>> SearchAsync(string collection, double[] vector, int k, CancellationToken ct = default)
{
var response = await _db.PostAsync<SearchListResponse>(
$"/vectors/{Uri.EscapeDataString(collection)}/search",
new { vector, k }, ct);
return response.Results ?? new List<SearchResult>();
}
public async Task DeleteAsync(string collection, IEnumerable<string> ids, CancellationToken ct = default)
{
await _db.DeleteAsync($"/vectors/{Uri.EscapeDataString(collection)}", new { ids = ids }, ct);
}
}
/// <summary>
/// Time series store operations.
/// </summary>
public class TimeSeriesStore
{
private readonly SynorDatabase _db;
internal TimeSeriesStore(SynorDatabase db) => _db = db;
public async Task WriteAsync(string series, IEnumerable<DataPoint> points, CancellationToken ct = default)
{
await _db.PostAsync<object>(
$"/timeseries/{Uri.EscapeDataString(series)}/write",
new { points = points }, ct);
}
public async Task<List<DataPoint>> QueryAsync(
string series, TimeRange range, Aggregation? aggregation = null, CancellationToken ct = default)
{
var body = new Dictionary<string, object> { ["range"] = range };
if (aggregation != null) body["aggregation"] = aggregation;
var response = await _db.PostAsync<DataPointListResponse>(
$"/timeseries/{Uri.EscapeDataString(series)}/query", body, ct);
return response.Points ?? new List<DataPoint>();
}
}
/// <summary>
/// Perform a health check.
/// </summary>
public async Task<bool> HealthCheckAsync(CancellationToken ct = default)
{
try
{
var response = await GetAsync<HealthResponse>("/health", ct);
return response.Status == "healthy";
}
catch
{
return false;
}
}
public bool IsClosed => _disposed;
public void Dispose()
{
if (!_disposed)
{
_httpClient.Dispose();
_disposed = true;
}
GC.SuppressFinalize(this);
}
// Private HTTP methods
internal async Task<T> GetAsync<T>(string path, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.GetAsync(path, ct);
await EnsureSuccessAsync(response);
return await response.Content.ReadFromJsonAsync<T>(_jsonOptions, ct)
?? throw new DatabaseException("Failed to deserialize response");
});
}
internal async Task<T> PostAsync<T>(string path, object body, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.PostAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(response);
var content = await response.Content.ReadAsStringAsync(ct);
if (string.IsNullOrEmpty(content)) return default!;
return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
});
}
internal async Task<T> PutAsync<T>(string path, object body, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.PutAsJsonAsync(path, body, _jsonOptions, ct);
await EnsureSuccessAsync(response);
var content = await response.Content.ReadAsStringAsync(ct);
if (string.IsNullOrEmpty(content)) return default!;
return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
});
}
internal async Task<T> PatchAsync<T>(string path, object body, CancellationToken ct)
{
return await ExecuteWithRetryAsync(async () =>
{
var request = new HttpRequestMessage(HttpMethod.Patch, path)
{
Content = JsonContent.Create(body, options: _jsonOptions)
};
var response = await _httpClient.SendAsync(request, ct);
await EnsureSuccessAsync(response);
var content = await response.Content.ReadAsStringAsync(ct);
if (string.IsNullOrEmpty(content)) return default!;
return JsonSerializer.Deserialize<T>(content, _jsonOptions)!;
});
}
internal async Task DeleteAsync(string path, CancellationToken ct)
{
await ExecuteWithRetryAsync(async () =>
{
var response = await _httpClient.DeleteAsync(path, ct);
await EnsureSuccessAsync(response);
return true;
});
}
internal async Task DeleteAsync(string path, object body, CancellationToken ct)
{
await ExecuteWithRetryAsync(async () =>
{
var request = new HttpRequestMessage(HttpMethod.Delete, path)
{
Content = JsonContent.Create(body, options: _jsonOptions)
};
var response = await _httpClient.SendAsync(request, ct);
await EnsureSuccessAsync(response);
return true;
});
}
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation)
{
Exception? lastError = null;
for (int attempt = 0; attempt < _config.Retries; attempt++)
{
try
{
return await operation();
}
catch (Exception ex)
{
lastError = ex;
if (_config.Debug) Console.WriteLine($"Attempt {attempt + 1} failed: {ex.Message}");
if (attempt < _config.Retries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(1 << attempt));
}
}
}
throw lastError ?? new DatabaseException("Unknown error");
}
private async Task EnsureSuccessAsync(HttpResponseMessage response)
{
if (!response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
var error = JsonSerializer.Deserialize<Dictionary<string, object>>(content, _jsonOptions);
var message = error?.GetValueOrDefault("message")?.ToString() ?? $"HTTP {(int)response.StatusCode}";
var code = error?.GetValueOrDefault("code")?.ToString();
throw new DatabaseException(message, code, (int)response.StatusCode);
}
}
}