Files
data-raft/Sources/DataRaft/Classes/ModelDatabaseService.swift

139 lines
4.7 KiB
Swift

import Foundation
import DataLiteCoder
/// A database service that provides model encoding and decoding support.
///
/// ## Overview
///
/// `ModelDatabaseService` extends ``DatabaseService`` by integrating `RowEncoder` and `RowDecoder`
/// to simplify model-based interactions with the database. Subclasses can encode Swift types into
/// SQLite rows and decode query results back into strongly typed models.
///
/// This enables a clean, type-safe persistence layer for applications that use Codable or custom
/// encodable/decodable types.
///
/// `ModelDatabaseService` serves as a foundation for higher-level model repositories and services.
/// It inherits all transactional and thread-safe behavior from ``DatabaseService`` while adding
/// automatic model serialization.
///
/// ## Usage
///
/// ```swift
/// struct User: Codable {
/// let id: Int
/// let name: String
/// }
///
/// final class UserService: ModelDatabaseService, @unchecked Sendable {
/// func fetchUser() throws -> User? {
/// try perform(in: .deferred) { connection in
/// let stmt = try connection.prepare(sql: "SELECT * FROM users")
/// guard try stmt.step(), let row = stmt.currentRow() else {
/// return nil
/// }
/// return try decoder.decode(User.self, from: row)
/// }
/// }
///
/// func insertUser(_ user: User) throws {
/// try perform(in: .immediate) { connection in
/// let row = try encoder.encode(user)
/// let columns = row.columns.joined(separator: ", ")
/// let placeholders = row.namedParameters.joined(separator: ", ")
/// let sql = "INSERT INTO users (\(columns)) VALUES (\(placeholders))"
/// let stmt = try connection.prepare(sql: sql)
/// try stmt.execute([row])
/// }
/// }
/// }
/// ```
///
/// ## Topics
///
/// ### Properties
///
/// - ``encoder``
/// - ``decoder``
///
/// ### Initializers
///
/// - ``init(provider:config:queue:center:encoder:decoder:)``
/// - ``init(provider:config:queue:)``
/// - ``init(connection:config:queue:)``
open class ModelDatabaseService: DatabaseService, @unchecked Sendable {
// MARK: - Properties
/// The encoder used to serialize models into row representations.
public let encoder: RowEncoder
/// The decoder used to deserialize database rows into model instances.
public let decoder: RowDecoder
// MARK: - Inits
/// Creates a model-aware database service.
///
/// - Parameters:
/// - provider: A closure that returns a new database connection.
/// - config: Optional configuration for the connection.
/// - queue: The dispatch queue used for serializing database operations.
/// - center: The notification center used for database events. Defaults to `.databaseCenter`.
/// - encoder: The encoder for converting models into SQLite rows.
/// - decoder: The decoder for converting rows back into model instances.
public init(
provider: @escaping ConnectionProvider,
config: ConnectionConfig? = nil,
queue: DispatchQueue? = nil,
center: NotificationCenter = .databaseCenter,
encoder: RowEncoder,
decoder: RowDecoder
) {
self.encoder = encoder
self.decoder = decoder
super.init(
provider: provider,
config: config,
queue: queue,
center: center
)
}
/// Creates a model-aware database service using default encoder and decoder instances.
///
/// - Parameters:
/// - provider: A closure that returns a new database connection.
/// - config: Optional configuration for the connection.
/// - queue: The dispatch queue used for serializing database operations.
public required init(
provider: @escaping ConnectionProvider,
config: ConnectionConfig? = nil,
queue: DispatchQueue? = nil
) {
self.encoder = .init()
self.decoder = .init()
super.init(
provider: provider,
config: config,
queue: queue
)
}
/// Creates a model-aware database service from a connection autoclosure.
///
/// - Parameters:
/// - provider: A connection autoclosure that returns a database connection.
/// - config: Optional configuration for the connection.
/// - queue: The dispatch queue used for serializing database operations.
public required convenience init(
connection provider: @escaping @autoclosure ConnectionProvider,
config: ConnectionConfig? = nil,
queue: DispatchQueue? = nil
) {
self.init(
provider: provider,
config: config,
queue: queue
)
}
}