Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
Abstract database component, providing a shared API surface for database drivers written in Swift.

[
![Release: 1.0.0-beta.2](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E2-F05138)
![Release: 1.0.0-beta.3](https://img.shields.io/badge/Release-1%2E0%2E0--beta%3E2-F05138)
](
https://github.com/feather-framework/feather-database/releases/tag/1.0.0-beta.2
https://github.com/feather-framework/feather-database/releases/tag/1.0.0-beta.3
)

## Features
Expand Down Expand Up @@ -35,7 +35,7 @@ Abstract database component, providing a shared API surface for database drivers
Use Swift Package Manager; add the dependency to your `Package.swift` file:

```swift
.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.1"),
.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"),
```

Then add `FeatherDatabase` to your target dependencies:
Expand All @@ -55,9 +55,6 @@ Then add `FeatherDatabase` to your target dependencies:

API documentation is available at the following link. Refer to the mock objects in the Tests directory if you want to build a custom database driver implementation.

> [!TIP]
> Avoid calling `database.execute` while in a transaction; use the transaction `connection.execute` instead.

> [!WARNING]
> This repository is a work in progress, things can break until it reaches v1.0.0.

Expand Down
58 changes: 8 additions & 50 deletions Sources/FeatherDatabase/DatabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,64 +18,22 @@ public protocol DatabaseClient: Sendable {
/// Execute work using a managed connection.
///
/// The connection is provided to the closure for the duration of the call.
/// - Parameters:
/// - isolation: The actor isolation to use for the duration of the call.
/// - closure: A closure that receives a connection and returns a result.
/// - Parameter: closure: A closure that receives a connection and returns a result.
/// - Throws: A `DatabaseError` if acquiring or using the connection fails.
/// - Returns: The result returned by the closure.
@discardableResult
func connection<T>(
isolation: isolated (any Actor)?,
_ closure: (Connection) async throws -> sending T,
) async throws(DatabaseError) -> sending T
func withConnection<T>(
_ closure: (Connection) async throws -> T,
) async throws(DatabaseError) -> T

/// Execute work inside a transaction.
///
/// Implementations should wrap the closure in a transaction boundary.
/// - Parameters:
/// - isolation: The actor isolation to use for the duration of the call.
/// - closure: A closure that receives a connection and returns a result.
/// - Parameter: closure: A closure that receives a connection and returns a result.
/// - Throws: A `DatabaseError` if the transaction fails.
/// - Returns: The result returned by the closure.
@discardableResult
func transaction<T>(
isolation: isolated (any Actor)?,
_ closure: (Connection) async throws -> sending T,
) async throws(DatabaseError) -> sending T

/// Execute a query using a managed connection.
///
/// This is a convenience wrapper around `connection(_:)`.
/// - Parameters:
/// - isolation: The actor isolation to use for the duration of the call.
/// - query: The query to execute.
/// - Throws: A `DatabaseError` if execution fails.
/// - Returns: The query result.
@discardableResult
func execute(
isolation: isolated (any Actor)?,
query: Connection.Query,
) async throws(DatabaseError) -> Connection.Result

}

extension DatabaseClient {

/// Execute a query using a managed connection.
///
/// This default implementation executes the query inside `connection(_:)`.
/// - Parameters:
/// - isolation: The actor isolation to use for the duration of the call.
/// - query: The query to execute.
/// - Throws: A `DatabaseError` if execution fails.
/// - Returns: The query result.
@discardableResult
public func execute(
isolation: isolated (any Actor)? = #isolation,
query: Connection.Query,
) async throws(DatabaseError) -> Connection.Result {
try await connection(isolation: isolation) { connection in
try await connection.execute(query: query)
}
}
func withTransaction<T>(
_ closure: (Connection) async throws -> T,
) async throws(DatabaseError) -> T
}
23 changes: 13 additions & 10 deletions Sources/FeatherDatabase/DatabaseConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,33 @@ import Logging
/// A connection that can execute database queries.
///
/// Implementations provide query execution and lifecycle management.
public protocol DatabaseConnection {
public protocol DatabaseConnection: Sendable {

/// The query type supported by this connection.
///
/// Use this to define the SQL and bindings type.
associatedtype Query: DatabaseQuery
/// The query result type produced by this connection.

/// The row sequence type produced by this connection.
///
/// The result must conform to `DatabaseQueryResult`.
associatedtype Result: DatabaseQueryResult
/// The result must conform to `DatabaseRowSequence`.
associatedtype RowSequence: DatabaseRowSequence

/// The logger used for connection operations.
///
/// This is used to record database-related diagnostics.
var logger: Logger { get }

/// Execute a query against the connection.
/// Runs a query using the database connection.
///
/// Implementations should translate errors to `DatabaseError`.
/// - Parameter query: The query to execute.
/// - Parameters:
/// - query: The query to execute.
/// - handler: A closure that transforms the result into a generic value.
/// - Throws: A `DatabaseError` if execution fails.
/// - Returns: The result of the query execution.
@discardableResult
func execute(
query: Query
) async throws(DatabaseError) -> Result
func run<T: Sendable>(
query: Query,
_ handler: (RowSequence) async throws -> T
) async throws(DatabaseError) -> T
}
45 changes: 9 additions & 36 deletions Sources/FeatherDatabase/DatabaseError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,24 @@
// Created by Tibor Bödecs on 2026. 01. 14..
//

/// A transaction error that captures failure details.
///
/// Use this protocol to report errors from transaction phases.
public protocol DatabaseTransactionError: Error, Sendable {
/// The source file where the transaction error was created.
///
/// This is typically populated using `#fileID`.
var file: String { get }
/// The source line where the transaction error was created.
///
/// This is typically populated using `#line`.
var line: Int { get }

/// The error thrown while beginning the transaction.
///
/// This is set when the begin step fails.
var beginError: Error? { get set }
/// The error thrown inside the transaction closure.
///
/// This is set when the closure fails before commit.
var closureError: Error? { get set }
/// The error thrown while committing the transaction.
///
/// This is set when the commit step fails.
var commitError: Error? { get set }
/// The error thrown while rolling back the transaction.
///
/// This is set when the rollback step fails.
var rollbackError: Error? { get set }
}

/// High-level database errors surfaced by the client API.
///
/// Use these cases to represent connection, query, and transaction failures.
public enum DatabaseError: Error, Sendable {
public enum DatabaseError: Error {

/// A connection-level failure.
///
/// The associated error provides the underlying cause.
case connection(Error)
/// A query execution failure.
///
/// The associated error provides the underlying cause.
case query(Error)

/// A transaction failure.
///
/// The associated error includes phase-specific details.
case transaction(DatabaseTransactionError)

/// A query execution failure.
///
/// The associated error provides the underlying cause.
case query(Error)

}
50 changes: 0 additions & 50 deletions Sources/FeatherDatabase/DatabaseQueryResult.swift

This file was deleted.

28 changes: 28 additions & 0 deletions Sources/FeatherDatabase/DatabaseRowSequence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// DatabaseRowSequence.swift
// feather-database
//
// Created by Tibor Bödecs on 2026. 01. 10..
//

/// A sequence returned from a database query.
///
/// Use this protocol to iterate rows asynchronously or collect them eagerly.
public protocol DatabaseRowSequence:
AsyncSequence,
Sendable
where
Element == Row
{
/// A row type produced by the query.
///
/// This row must conform to `DatabaseRow` for decoding support.
associatedtype Row: DatabaseRow

/// Collect all rows from the sequence.
///
/// This method consumes the sequence and returns all elements.
/// - Throws: An error if iteration fails.
/// - Returns: An array of all rows produced by the query.
func collect() async throws -> [Element]
}
37 changes: 37 additions & 0 deletions Sources/FeatherDatabase/DatabaseTransactionError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// DatabaseTransactionError.swift
// feather-database
//
// Created by Tibor Bödecs on 2026. 02. 01..
//

/// A transaction error that captures failure details.
///
/// Use this protocol to report errors from transaction phases.
public protocol DatabaseTransactionError: Error {
/// The source file where the transaction error was created.
///
/// This is typically populated using `#fileID`.
var file: String { get }
/// The source line where the transaction error was created.
///
/// This is typically populated using `#line`.
var line: Int { get }

/// The error thrown while beginning the transaction.
///
/// This is set when the begin step fails.
var beginError: Error? { get }
/// The error thrown inside the transaction closure.
///
/// This is set when the closure fails before commit.
var closureError: Error? { get }
/// The error thrown while committing the transaction.
///
/// This is set when the commit step fails.
var commitError: Error? { get }
/// The error thrown while rolling back the transaction.
///
/// This is set when the rollback step fails.
var rollbackError: Error? { get }
}
Loading