synor/sdk/swift/Sources/Synor/Database/SynorDatabase.swift
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

369 lines
12 KiB
Swift

import Foundation
/// Synor Database SDK client for Swift.
///
/// Provides multi-model database with Key-Value, Document, Vector, and Time Series stores.
///
/// Example:
/// ```swift
/// let db = SynorDatabase(config: DatabaseConfig(apiKey: "your-api-key"))
///
/// // Key-Value operations
/// try await db.kv.set(key: "mykey", value: "myvalue", ttl: 3600)
/// let value = try await db.kv.get(key: "mykey")
///
/// // Document operations
/// let docId = try await db.documents.create(collection: "users", document: ["name": "Alice", "age": 30])
/// let doc = try await db.documents.get(collection: "users", id: docId)
///
/// // Vector operations
/// try await db.vectors.upsert(collection: "embeddings", vectors: [
/// VectorEntry(id: "vec1", vector: [0.1, 0.2, 0.3], metadata: ["label": "test"])
/// ])
///
/// // Time series operations
/// try await db.timeseries.write(series: "metrics", points: [
/// DataPoint(timestamp: Date().timeIntervalSince1970, value: 42.0)
/// ])
/// ```
public class SynorDatabase {
private let config: DatabaseConfig
private let session: URLSession
private let decoder: JSONDecoder
private let encoder: JSONEncoder
private var closed = false
/// Key-Value store operations.
public private(set) lazy var kv = KeyValueStore(db: self)
/// Document store operations.
public private(set) lazy var documents = DocumentStore(db: self)
/// Vector store operations.
public private(set) lazy var vectors = VectorStore(db: self)
/// Time series store operations.
public private(set) lazy var timeseries = TimeSeriesStore(db: self)
public init(config: DatabaseConfig) {
self.config = config
let sessionConfig = URLSessionConfiguration.default
sessionConfig.timeoutIntervalForRequest = config.timeout
sessionConfig.timeoutIntervalForResource = config.timeout * 2
self.session = URLSession(configuration: sessionConfig)
self.decoder = JSONDecoder()
self.encoder = JSONEncoder()
}
// MARK: - Key-Value Store
/// Key-Value store for simple key-value operations.
public class KeyValueStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Get a value by key.
public func get(key: String) async throws -> Any? {
let response: KVGetResponse = try await db.request(
method: "GET",
path: "/kv/\(key.urlEncoded)"
)
return response.value?.value
}
/// Set a value for a key with optional TTL.
public func set(key: String, value: Any, ttl: Int? = nil) async throws {
var body: [String: AnyCodable] = [
"key": AnyCodable(key),
"value": AnyCodable(value)
]
if let ttl = ttl {
body["ttl"] = AnyCodable(ttl)
}
let _: EmptyResponse = try await db.request(
method: "PUT",
path: "/kv/\(key.urlEncoded)",
body: body
)
}
/// Delete a key.
public func delete(key: String) async throws {
let _: EmptyResponse = try await db.request(
method: "DELETE",
path: "/kv/\(key.urlEncoded)"
)
}
/// List keys by prefix.
public func list(prefix: String) async throws -> [KeyValue] {
let response: KVListResponse = try await db.request(
method: "GET",
path: "/kv?prefix=\(prefix.urlEncoded)"
)
return response.items ?? []
}
}
// MARK: - Document Store
/// Document store for document-oriented operations.
public class DocumentStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Create a new document.
public func create(collection: String, document: [String: Any]) async throws -> String {
let body = document.mapValues { AnyCodable($0) }
let response: CreateDocumentResponse = try await db.request(
method: "POST",
path: "/collections/\(collection.urlEncoded)/documents",
body: body
)
return response.id
}
/// Get a document by ID.
public func get(collection: String, id: String) async throws -> Document {
return try await db.request(
method: "GET",
path: "/collections/\(collection.urlEncoded)/documents/\(id.urlEncoded)"
)
}
/// Update a document.
public func update(collection: String, id: String, update: [String: Any]) async throws {
let body = update.mapValues { AnyCodable($0) }
let _: EmptyResponse = try await db.request(
method: "PATCH",
path: "/collections/\(collection.urlEncoded)/documents/\(id.urlEncoded)",
body: body
)
}
/// Delete a document.
public func delete(collection: String, id: String) async throws {
let _: EmptyResponse = try await db.request(
method: "DELETE",
path: "/collections/\(collection.urlEncoded)/documents/\(id.urlEncoded)"
)
}
/// Query documents.
public func query(collection: String, query: DocumentQuery) async throws -> [Document] {
let response: DocumentListResponse = try await db.request(
method: "POST",
path: "/collections/\(collection.urlEncoded)/query",
body: query
)
return response.documents ?? []
}
}
// MARK: - Vector Store
/// Vector store for similarity search operations.
public class VectorStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Upsert vectors into a collection.
public func upsert(collection: String, vectors: [VectorEntry]) async throws {
let body: [String: [VectorEntry]] = ["vectors": vectors]
let _: EmptyResponse = try await db.request(
method: "POST",
path: "/vectors/\(collection.urlEncoded)/upsert",
body: body
)
}
/// Search for similar vectors.
public func search(collection: String, vector: [Double], k: Int) async throws -> [SearchResult] {
let body: [String: AnyCodable] = [
"vector": AnyCodable(vector),
"k": AnyCodable(k)
]
let response: SearchListResponse = try await db.request(
method: "POST",
path: "/vectors/\(collection.urlEncoded)/search",
body: body
)
return response.results ?? []
}
/// Delete vectors by IDs.
public func delete(collection: String, ids: [String]) async throws {
let body: [String: [String]] = ["ids": ids]
let _: EmptyResponse = try await db.request(
method: "DELETE",
path: "/vectors/\(collection.urlEncoded)",
body: body
)
}
}
// MARK: - Time Series Store
/// Time series store for time-based data operations.
public class TimeSeriesStore {
private let db: SynorDatabase
internal init(db: SynorDatabase) {
self.db = db
}
/// Write data points to a series.
public func write(series: String, points: [DataPoint]) async throws {
let body: [String: [DataPoint]] = ["points": points]
let _: EmptyResponse = try await db.request(
method: "POST",
path: "/timeseries/\(series.urlEncoded)/write",
body: body
)
}
/// Query data points from a series.
public func query(series: String, range: TimeRange, aggregation: Aggregation? = nil) async throws -> [DataPoint] {
var body: [String: AnyCodable] = ["range": AnyCodable(["start": range.start, "end": range.end])]
if let aggregation = aggregation {
body["aggregation"] = AnyCodable(["function": aggregation.function, "interval": aggregation.interval])
}
let response: DataPointListResponse = try await db.request(
method: "POST",
path: "/timeseries/\(series.urlEncoded)/query",
body: body
)
return response.points ?? []
}
}
// MARK: - Lifecycle
/// Close the client.
public func close() {
closed = true
session.invalidateAndCancel()
}
/// Check if the client is closed.
public var isClosed: Bool { closed }
/// Perform a health check.
public func healthCheck() async -> Bool {
do {
let response: HealthResponse = try await request(method: "GET", path: "/health")
return response.status == "healthy"
} catch {
return false
}
}
// MARK: - Internal HTTP Methods
internal func request<T: Decodable>(method: String, path: String) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: method, path: path, body: nil as EmptyBody?)
}
}
internal func request<T: Decodable, R: Encodable>(method: String, path: String, body: R) async throws -> T {
return try await executeWithRetry {
try await self.performRequest(method: method, path: path, body: body)
}
}
private func performRequest<T: Decodable, R: Encodable>(
method: String,
path: String,
body: R?
) async throws -> T {
if closed { throw DatabaseError.clientClosed }
guard let url = URL(string: "\(config.endpoint)\(path)") else {
throw DatabaseError.invalidResponse
}
var request = URLRequest(url: url)
request.httpMethod = method
request.setValue("Bearer \(config.apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("swift/0.1.0", forHTTPHeaderField: "X-SDK-Version")
if let body = body {
do {
request.httpBody = try encoder.encode(body)
} catch {
throw DatabaseError.encodingError(error)
}
}
let (data, response): (Data, URLResponse)
do {
(data, response) = try await session.data(for: request)
} catch {
throw DatabaseError.networkError(error)
}
guard let httpResponse = response as? HTTPURLResponse else {
throw DatabaseError.invalidResponse
}
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
if data.isEmpty && T.self == EmptyResponse.self {
return EmptyResponse() as! T
}
do {
return try decoder.decode(T.self, from: data)
} catch {
throw DatabaseError.decodingError(error)
}
}
let errorInfo = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
let message = errorInfo?["message"] as? String ?? "HTTP \(httpResponse.statusCode)"
let code = errorInfo?["code"] as? String
throw DatabaseError.httpError(statusCode: httpResponse.statusCode, message: message, code: code)
}
private func executeWithRetry<T>(_ operation: () async throws -> T) async throws -> T {
var lastError: Error?
for attempt in 0..<config.retries {
do {
return try await operation()
} catch {
lastError = error
if config.debug {
print("Attempt \(attempt + 1) failed: \(error)")
}
if attempt < config.retries - 1 {
try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * (1 << attempt)))
}
}
}
throw lastError ?? DatabaseError.invalidResponse
}
}
// MARK: - Private Helpers
private struct EmptyBody: Encodable {}
private struct EmptyResponse: Decodable {}
private extension String {
var urlEncoded: String {
addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? self
}
}