Skip to content

Commit e225005

Browse files
authored
Add service lifecycle support via package trait (#6)
- implemented service lifecycle support - add new package trait - updated tests
1 parent 73f456f commit e225005

6 files changed

Lines changed: 173 additions & 13 deletions

File tree

Package.resolved

Lines changed: 26 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,19 @@ let package = Package(
3434
products: [
3535
.library(name: "FeatherSQLiteDatabase", targets: ["FeatherSQLiteDatabase"]),
3636
],
37+
traits: [
38+
"ServiceLifecycleSupport",
39+
.default(
40+
enabledTraits: [
41+
"ServiceLifecycleSupport",
42+
]
43+
),
44+
],
3745
dependencies: [
3846
.package(url: "https://github.com/apple/swift-log", from: "1.6.0"),
3947
.package(url: "https://github.com/vapor/sqlite-nio", from: "1.12.0"),
4048
.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"),
49+
.package(url: "https://github.com/swift-server/swift-service-lifecycle", from: "2.8.0"),
4150
// [docc-plugin-placeholder]
4251
],
4352
targets: [
@@ -52,8 +61,13 @@ let package = Package(
5261
.target(
5362
name: "FeatherSQLiteDatabase",
5463
dependencies: [
55-
.product(name: "FeatherDatabase", package: "feather-database"),
5664
.target(name: "SQLiteNIOExtras"),
65+
.product(name: "FeatherDatabase", package: "feather-database"),
66+
.product(
67+
name: "ServiceLifecycle",
68+
package: "swift-service-lifecycle",
69+
condition: .when(traits: ["ServiceLifecycleSupport"])
70+
),
5771
],
5872
swiftSettings: defaultSwiftSettings
5973
),

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,30 @@ Then add `FeatherSQLiteDatabase` to your target dependencies:
4545
.product(name: "FeatherSQLiteDatabase", package: "feather-sqlite-database"),
4646
```
4747

48+
<<<<<<< HEAD
49+
### Package traits
50+
51+
This package offers additional integrations you can enable using [package traits](https://docs.swift.org/swiftpm/documentation/packagemanagerdocs/addingdependencies#Packages-with-Traits).
52+
To enable an additional trait on the package, update the package dependency:
53+
54+
```diff
55+
.package(
56+
url: "https://github.com/feather-framework/feather-sqlite-database",
57+
exact: "1.0.0-beta.3",
58+
+ traits: [
59+
+ .defaults,
60+
+ "ServiceLifecycleSupport",
61+
+ ]
62+
)
63+
```
64+
65+
Available traits:
66+
67+
- `ServiceLifecycleSupport` (default): Adds support for `SQLiteClientService`, a `ServiceLifecycle.Service` implementation for managing SQLite clients.
68+
69+
70+
=======
71+
>>>>>>> main
4872
## Usage
4973

5074
API documentation is available at the link below:
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//
2+
// SQLiteDatabaseService.swift
3+
// feather-sqlite-database
4+
//
5+
// Created by Tibor Bödecs on 2026. 01. 29..
6+
//
7+
8+
#if ServiceLifecycleSupport
9+
10+
import SQLiteNIOExtras
11+
import ServiceLifecycle
12+
13+
/// A `Service` wrapper around an `SQLiteClient`.
14+
public struct SQLiteDatabaseService: Service {
15+
16+
/// The underlying SQLite client instance.
17+
public var client: SQLiteClient
18+
19+
/// Creates a new SQLite client service.
20+
///
21+
/// - Parameter client: The SQLite client to manage for the service lifecycle.
22+
public init(
23+
_ client: SQLiteClient
24+
) {
25+
self.client = client
26+
}
27+
28+
/// Runs the SQLite client service.
29+
///
30+
/// This method starts the SQLite client, waits for a graceful shutdown
31+
/// signal, and then shuts down the client in an orderly manner.
32+
///
33+
/// - Throws: Rethrows any error produced while starting the SQLite client.
34+
public func run() async throws {
35+
try await client.run()
36+
try? await gracefulShutdown()
37+
await client.shutdown()
38+
}
39+
40+
}
41+
#endif

Sources/SQLiteNIOExtras/SQLiteConnectionPool.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,9 @@ actor SQLiteConnectionPool {
208208
catch {
209209
configuration.logger.warning(
210210
"Failed to close SQLite connection after setup error",
211-
metadata: ["error": "\(error)"]
211+
metadata: [
212+
"error": "\(error)"
213+
]
212214
)
213215
}
214216
throw error
@@ -225,7 +227,9 @@ actor SQLiteConnectionPool {
225227
catch {
226228
configuration.logger.warning(
227229
"Failed to close SQLite connection",
228-
metadata: ["error": "\(error)"]
230+
metadata: [
231+
"error": "\(error)"
232+
]
229233
)
230234
}
231235
}

Tests/FeatherSQLiteDatabaseTests/SQLiteDatabaseTestSuite.swift renamed to Tests/FeatherSQLiteDatabaseTests/FeatherSQLiteDatabaseTestSuite.swift

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// SQLiteDatabaseTestSuite.swift
2+
// FeatherSQLiteDatabaseTestSuite.swift
33
// feather-sqlite-database
44
//
55
// Created by Tibor Bödecs on 2026. 01. 10..
@@ -14,7 +14,7 @@ import Testing
1414
@testable import FeatherSQLiteDatabase
1515

1616
@Suite
17-
struct SQLiteDatabaseTestSuite {
17+
struct FeatherSQLiteDatabaseTestSuite {
1818

1919
private func runUsingTestDatabaseClient(
2020
_ closure: ((SQLiteDatabaseClient) async throws -> Void)
@@ -775,3 +775,62 @@ struct SQLiteDatabaseTestSuite {
775775
}
776776
}
777777
}
778+
779+
#if ServiceLifecycleSupport
780+
import ServiceLifecycle
781+
782+
extension FeatherSQLiteDatabaseTestSuite {
783+
784+
@Test
785+
func serviceLifecycleSupport() async throws {
786+
var logger = Logger(label: "test")
787+
logger.logLevel = .info
788+
789+
let configuration = SQLiteClient.Configuration(
790+
storage: .memory,
791+
logger: logger,
792+
)
793+
let client = SQLiteClient(configuration: configuration)
794+
let database = SQLiteDatabaseClient(client: client, logger: logger)
795+
let service = SQLiteDatabaseService(client)
796+
797+
let serviceGroup = ServiceGroup(
798+
services: [service],
799+
logger: logger
800+
)
801+
802+
try await withThrowingTaskGroup(of: Void.self) { group in
803+
group.addTask {
804+
try await serviceGroup.run()
805+
}
806+
group.addTask {
807+
let result = try await database.withConnection { connection in
808+
try await connection.run(
809+
query: #"""
810+
SELECT
811+
sqlite_version() AS "version"
812+
WHERE
813+
1=\#(1);
814+
"""#
815+
)
816+
}
817+
818+
let resultArray = try await result.collect()
819+
#expect(resultArray.count == 1)
820+
821+
let item = resultArray[0]
822+
let version = try item.decode(
823+
column: "version",
824+
as: String.self
825+
)
826+
#expect(version.split(separator: ".").count == 3)
827+
}
828+
try await group.next()
829+
830+
try await Task.sleep(for: .milliseconds(100))
831+
832+
await serviceGroup.triggerGracefulShutdown()
833+
}
834+
}
835+
}
836+
#endif

0 commit comments

Comments
 (0)