From 7653778d69587d65afb6d7278b7a8b62317f0dd7 Mon Sep 17 00:00:00 2001 From: Scott Marchant Date: Mon, 6 Jul 2026 12:33:06 -0600 Subject: [PATCH 1/3] feat(embedded): rebase the embedded port onto feat/khasmPAL-2026 (khasm's pinned base), Embedded-only gating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replays the feat/swift-wasm-embedded-poc work (embedded SQLiteData encoder/decoder stubs, Codable row-decode gates, dynamic-cast-free version comparison) on top of the exact sqlite-kit revision khasm's Package.resolved pins (d6a7f120, feat/khasmPAL-2026) instead of the upstream-main base. The NIO-flavored gates become `#if hasFeature(Embedded)`; the khasmPAL NIOAsyncRuntime-based SQLiteConnectionSource is restored for regular WASI and gated out only on Embedded. NIO products + AsyncKit are dropped from the manifest only with KHASM_EMBEDDED=1; swift-nio/async-kit stay on the PassiveLogic fork URLs (NOT local path clones — transitive path deps override identities in the khasm root graph and the local swift-nio clone is not flavor-safe). sqlite-nio/sql-kit resolve to the local embedded clones. Prior upstream-main-based branch preserved as feat/swift-wasm-embedded-poc-upstream-base. Verified: embedded SDK build (KHASM_EMBEDDED=1) green; native green. Co-Authored-By: Claude Fable 5 --- Package@swift-5.9.swift | 35 +++++++++++++------ Sources/SQLiteKit/Exports.swift | 2 ++ Sources/SQLiteKit/SQLiteConfiguration.swift | 6 ++++ .../SQLiteKit/SQLiteConnection+SQLKit.swift | 30 ++++++++++++---- .../SQLiteKit/SQLiteConnectionSource.swift | 5 +++ Sources/SQLiteKit/SQLiteDataDecoder.swift | 15 ++++++++ Sources/SQLiteKit/SQLiteDataEncoder.swift | 29 +++++++++++++++ Sources/SQLiteKit/SQLiteRow+SQLRow.swift | 4 +++ 8 files changed, 109 insertions(+), 17 deletions(-) diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index f1b5050..4ad1818 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -1,6 +1,15 @@ // swift-tools-version:5.9 +import class Foundation.ProcessInfo import PackageDescription +// Embedded-wasm port (see /Users/scottm/git/c34/khasm/EMBEDDED_PORT_PLAN.md): with +// KHASM_EMBEDDED=1 the SwiftNIO stack and AsyncKit's connection pool are dropped — the +// Embedded build uses sqlite-nio's NIO-free Swift-Concurrency driver directly, gated in +// source with `#if hasFeature(Embedded)` (not available in manifests, hence the env var, +// matching khasm's own manifest gating). Regular builds — including regular WASI via +// NIOAsyncRuntime — keep the NIO stack exactly as on the feat/khasmPAL-2026 base. +let khasmEmbedded = ProcessInfo.processInfo.environment["KHASM_EMBEDDED"] == "1" + let package = Package( name: "sqlite-kit", platforms: [ @@ -13,28 +22,32 @@ let package = Package( .library(name: "SQLiteKit", targets: ["SQLiteKit"]), ], dependencies: [ - // TODO: SM: Update swift-nio version once NIOAsyncRuntime is available from swift-nio - // .package(url: "https://github.com/apple/swift-nio.git", from: "2.89.0"), + // swift-nio / async-kit stay on the PassiveLogic fork URLs (feat/khasmPAL-2026, as on + // the base branch) rather than local clones: the local swift-nio clone carries the + // vestigial Option-1/2 embedded-NIO commits whose os(WASI) gates would change + // regular-WASI NIO behavior, and transitive *path* deps override URL declarations by + // identity in a consuming root graph (khasm). Under KHASM_EMBEDDED=1 both are dropped. .package(url: "https://github.com/PassiveLogic/swift-nio.git", branch: "feat/khasmPAL-2026"), - - // TODO: SM: Update below once everything is merged and release to the proper repositories -// .package(url: "https://github.com/vapor/sqlite-nio.git", from: "1.9.0"), - .package(url: "https://github.com/PassiveLogic/sqlite-nio.git", branch: "feat/khasmPAL-2026"), - .package(url: "https://github.com/vapor/sql-kit.git", from: "3.33.1"), -// .package(url: "https://github.com/vapor/async-kit.git", from: "1.19.0"), .package(url: "https://github.com/PassiveLogic/async-kit.git", branch: "feat/khasmPAL-2026"), + // Local embedded-ported clones (see /Users/scottm/git/c34/EMBEDDED_WASM_NOTES.md), + // branched from the same revisions khasm's Package.resolved pins, so regular + // (non-embedded) builds see identical sources. + .package(path: "../sqlite-nio"), + .package(path: "../sql-kit"), ], targets: [ .target( name: "SQLiteKit", dependencies: [ + .product(name: "SQLiteNIO", package: "sqlite-nio"), + .product(name: "SQLKit", package: "sql-kit"), + ] + (khasmEmbedded ? [] : [ + // Dropped on the Embedded build (KHASM_EMBEDDED=1, see note at the top). .product(name: "NIOFoundationCompat", package: "swift-nio"), .product(name: "NIOAsyncRuntime", package: "swift-nio", condition: .when(platforms: [.wasi])), .product(name: "NIOPosix", package: "swift-nio"), .product(name: "AsyncKit", package: "async-kit"), - .product(name: "SQLiteNIO", package: "sqlite-nio"), - .product(name: "SQLKit", package: "sql-kit"), - ], + ]), swiftSettings: swiftSettings ), .testTarget( diff --git a/Sources/SQLiteKit/Exports.swift b/Sources/SQLiteKit/Exports.swift index 6656d7a..2f9e727 100644 --- a/Sources/SQLiteKit/Exports.swift +++ b/Sources/SQLiteKit/Exports.swift @@ -1,4 +1,6 @@ @_documentation(visibility: internal) @_exported import SQLKit @_documentation(visibility: internal) @_exported import SQLiteNIO +#if !hasFeature(Embedded) // AsyncKit (connection pool) is elided on the NIO-free Embedded build. @_documentation(visibility: internal) @_exported import AsyncKit +#endif @_documentation(visibility: internal) @_exported import struct Logging.Logger diff --git a/Sources/SQLiteKit/SQLiteConfiguration.swift b/Sources/SQLiteKit/SQLiteConfiguration.swift index 66e8857..e82115b 100644 --- a/Sources/SQLiteKit/SQLiteConfiguration.swift +++ b/Sources/SQLiteKit/SQLiteConfiguration.swift @@ -1,4 +1,6 @@ +#if !hasFeature(Embedded) import struct Foundation.UUID +#endif /// Describes a configuration for an SQLite database connection. public struct SQLiteConfiguration: Sendable { @@ -8,7 +10,11 @@ public struct SQLiteConfiguration: Sendable { /// /// See ``memory(identifier:)``. public static var memory: Self { + #if hasFeature(Embedded) // Foundation.UUID is unavailable; derive a unique identifier from system randomness. + .memory(identifier: "memory-\(UInt64.random(in: .min ... .max))") + #else .memory(identifier: UUID().uuidString) + #endif } /// Specify an SQLite database stored in memory, using a given identifier string. diff --git a/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift b/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift index 5bd69ce..132f162 100644 --- a/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift +++ b/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift @@ -143,11 +143,15 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { Self.components(of: self.intValue).patch } + #if !hasFeature(Embedded) + // `as? Self` is a cast to a generic type, which Embedded Swift forbids. On embedded we fall back to + // the `SQLDatabaseReportedVersion` protocol's default `stringValue`-based comparison implementations. + // See `SQLDatabaseReportedVersion.isEqual(to:)`. func isEqual(to otherVersion: any SQLDatabaseReportedVersion) -> Bool { (otherVersion as? Self).map { $0.intValue == self.intValue } ?? false } - + // See `SQLDatabaseReportedVersion.isOlder(than:)`. func isOlder(than otherVersion: any SQLDatabaseReportedVersion) -> Bool { (otherVersion as? Self).map { @@ -156,6 +160,7 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { (self.patchVersion < $0.patchVersion))) } ?? false } + #endif } /// Wraps a `SQLiteDatabase` with the `SQLDatabase` protocol. @@ -173,12 +178,14 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { @usableFromInline let decoder: SQLiteDataDecoder + #if !hasFeature(Embedded) // See `SQLDatabase.eventLoop`. @usableFromInline var eventLoop: any EventLoop { self.database.eventLoop } - + #endif + // See `SQLDatabase.version`. @usableFromInline var version: (any SQLDatabaseReportedVersion)? { @@ -209,6 +216,7 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { self.queryLogLevel = queryLogLevel } + #if !hasFeature(Embedded) // See `SQLDatabase.execute(sql:_:)`. @usableFromInline func execute( @@ -216,7 +224,7 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { _ onRow: @escaping @Sendable (any SQLRow) -> () ) -> EventLoopFuture { let (sql, rawBinds) = self.serialize(query) - + if let queryLogLevel = self.queryLogLevel { self.logger.log(level: queryLogLevel, "Executing query", metadata: ["sql": .string(sql), "binds": .array(rawBinds.map { .string("\($0)") })]) } @@ -227,13 +235,14 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { } catch { return self.eventLoop.makeFailedFuture(error) } - + return self.database.query( sql, binds, { onRow($0.sql(decoder: self.decoder)) } ) } + #endif // See `SQLDatabase.execute(sql:_:)`. @usableFromInline @@ -242,9 +251,14 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { _ onRow: @escaping @Sendable (any SQLRow) -> () ) async throws { let (sql, rawBinds) = self.serialize(query) - + if let queryLogLevel = self.queryLogLevel { + // Embedded Swift has no reflection, so bound values cannot be string-interpolated for logging. + #if hasFeature(Embedded) + self.logger.log(level: queryLogLevel, "Executing query", metadata: ["sql": .string(sql)]) + #else self.logger.log(level: queryLogLevel, "Executing query", metadata: ["sql": .string(sql), "binds": .array(rawBinds.map { .string("\($0)") })]) + #endif } try await self.database.query( @@ -253,7 +267,10 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { { onRow($0.sql(decoder: self.decoder)) } ) } - + + #if !hasFeature(Embedded) + // `withSession(_:)` is gated out of the embedded `SQLDatabase` protocol (it is a generic requirement + // and relies on the NIO-based `withConnection`, which is concrete-only on WASI). // See `SQLDatabase.withSession(_:)`. @usableFromInline func withSession(_ closure: @escaping @Sendable (any SQLDatabase) async throws -> R) async throws -> R { @@ -261,4 +278,5 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { try await closure($0.sql(encoder: self.encoder, decoder: self.decoder, queryLogLevel: self.queryLogLevel)) } } + #endif } diff --git a/Sources/SQLiteKit/SQLiteConnectionSource.swift b/Sources/SQLiteKit/SQLiteConnectionSource.swift index 92eaf78..bb7c898 100644 --- a/Sources/SQLiteKit/SQLiteConnectionSource.swift +++ b/Sources/SQLiteKit/SQLiteConnectionSource.swift @@ -1,3 +1,7 @@ +// The connection-pool source is built on AsyncKit/SwiftNIO/Foundation, none of which are part of +// the NIO-free Embedded build. Embedded uses sqlite-nio's concrete async `SQLiteConnection` +// directly (no pool). +#if !hasFeature(Embedded) #if canImport(Darwin) import Foundation #else @@ -117,3 +121,4 @@ fileprivate extension SQLiteConfiguration.Storage { } } } +#endif // !hasFeature(Embedded) diff --git a/Sources/SQLiteKit/SQLiteDataDecoder.swift b/Sources/SQLiteKit/SQLiteDataDecoder.swift index 68fe89a..5489b31 100644 --- a/Sources/SQLiteKit/SQLiteDataDecoder.swift +++ b/Sources/SQLiteKit/SQLiteDataDecoder.swift @@ -1,3 +1,17 @@ +#if hasFeature(Embedded) +import SQLiteNIO + +/// Placeholder ``SQLiteDataDecoder`` for the Embedded Swift build. +/// +/// `Decodable`-based row decoding (the only thing this type does on normal platforms) is unavailable in +/// Embedded Swift, where ``SQLKit/SQLRow`` exposes no generic `decode(column:as:)`. Typed access on +/// embedded is performed directly against the concrete `SQLiteRow` via `SQLiteDataConvertible`. This +/// stub exists only so the `SQLiteDataDecoder` API surface (e.g. `sql(decoder:)`) remains uniform. +public struct SQLiteDataDecoder: Sendable { + /// Initialize a ``SQLiteDataDecoder``. + public init() {} +} +#else import Foundation import SQLiteNIO @_spi(CodableUtilities) import SQLKit @@ -109,3 +123,4 @@ public struct SQLiteDataDecoder: Sendable { } } } +#endif // hasFeature(Embedded) diff --git a/Sources/SQLiteKit/SQLiteDataEncoder.swift b/Sources/SQLiteKit/SQLiteDataEncoder.swift index 97e7ed3..938b565 100644 --- a/Sources/SQLiteKit/SQLiteDataEncoder.swift +++ b/Sources/SQLiteKit/SQLiteDataEncoder.swift @@ -1,3 +1,31 @@ +#if hasFeature(Embedded) +import SQLKit +import SQLiteNIO + +/// Translates a bound query parameter (a ``SQLKit/SQLBindValue``) into an `SQLiteData` value. +/// +/// The Codable/JSON path used on normal platforms is unavailable in Embedded Swift, so on that target +/// values are mapped directly from their driver-neutral ``SQLKit/SQLDataValue`` representation. +public struct SQLiteDataEncoder: Sendable { + /// Initialize a ``SQLiteDataEncoder``. + public init() {} + + /// Convert the given bound value to an `SQLiteData` value. + /// + /// - Parameter value: The value to convert. + /// - Returns: The converted `SQLiteData` value. + public func encode(_ value: any SQLBindValue & Sendable) throws -> SQLiteData { + switch value.sqlDataValue { + case .integer(let int): return .integer(SQLiteInt64(int)) + case .double(let double): return .float(double) + case .string(let string): return .text(string) + case .blob(let bytes): return .blob(bytes) + case .bool(let bool): return .integer(bool ? 1 : 0) + case .null: return .null + } + } +} +#else import NIOCore import Foundation @_spi(CodableUtilities) import SQLKit @@ -175,3 +203,4 @@ public struct SQLiteDataEncoder: Sendable { } } } +#endif // hasFeature(Embedded) diff --git a/Sources/SQLiteKit/SQLiteRow+SQLRow.swift b/Sources/SQLiteKit/SQLiteRow+SQLRow.swift index 77edf2f..dc03a40 100644 --- a/Sources/SQLiteKit/SQLiteRow+SQLRow.swift +++ b/Sources/SQLiteKit/SQLiteRow+SQLRow.swift @@ -43,6 +43,7 @@ private struct SQLiteSQLRow: SQLRow { return data == .null } + #if !hasFeature(Embedded) // `SQLRow.decode(column:as:)` is gated out of the embedded protocol (generic + Decodable). // See `SQLRow.decode(column:as:)`. func decode(column: String, as: D.Type) throws -> D { guard let data = self.row.column(column) else { @@ -50,6 +51,7 @@ private struct SQLiteSQLRow: SQLRow { } return try self.decoder.decode(D.self, from: data) } + #endif } /// A legacy deprecated conformance of `SQLiteRow` directly to `SQLRow`. This interface exists solely @@ -68,6 +70,7 @@ extension SQLiteNIO.SQLiteRow: SQLKit.SQLRow { // See `SQLRow.decodeNil(column:)`. public func decodeNil(column: String) throws -> Bool { (self.column(column) ?? .null) == .null } + #if !hasFeature(Embedded) // Decodable-based row decoding is unavailable in embedded Swift. // See `SQLRow.decode(column:as:)`. public func decode(column: String, as: D.Type) throws -> D { guard let data = self.column(column) else { throw MissingColumn(column: column) } @@ -87,4 +90,5 @@ extension SQLiteNIO.SQLiteRow: SQLKit.SQLRow { // See `SQLRow.decode(model:with:)`. public func decode(model: D.Type, with: SQLRowDecoder) throws -> D { try with.decode(D.self, from: self) } + #endif // !hasFeature(Embedded) } From 1618ed2cdf123ada7ad7b031673876e53a452f91 Mon Sep 17 00:00:00 2001 From: Scott Marchant Date: Fri, 10 Jul 2026 09:34:37 -0600 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20NativeConcurrency=20package=20trait?= =?UTF-8?q?=20=E2=80=94=20NIO/AsyncKit-free=20SQLiteKit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SwiftPM traits via a versioned Package@swift-6.1.swift, folding the former Package@swift-5.9.swift port manifest into it (two manifests total: the 5.8 base for old toolchains + the 6.1 traits manifest): - traits: `NIO` (default; SwiftNIO + AsyncKit exactly as today) and `NativeConcurrency` (NIO-free: no connection-pool source, async-only surface over sqlite-nio's NativeConcurrency driver). The NIO/AsyncKit products carry `.when(traits: ["NIO"])`; the KHASM_EMBEDDED env conditional is gone. The sqlite-nio and sql-kit edges forward `default`/`NativeConcurrency` conditionally on this package's own traits. - Source: NIO-swap gates re-keyed from `#if hasFeature(Embedded)` to `#if NativeConcurrency` (AsyncKit re-export, SQLiteConnectionSource whole-file, the SQLDatabase wrapper's eventLoop/future-execute/withSession). New for the (Foundation ∧ NativeConcurrency) combination: the Codable SQLiteDataDecoder's JSON blob fallback reads [UInt8] blobs, and the Codable encoder/decoder no longer import NIOCore/NIOFoundationCompat. Genuine Embedded language-limit gates (Codable coders' embedded stubs, UUID, `as? Self`, decode) stay on `hasFeature(Embedded)`. - Accepted loss under NativeConcurrency: no `ConnectionPoolSource`. Note: this commit keeps the branch-base remote URLs for PR-cuttability; the khasm-graph path wiring lands separately. Co-Authored-By: Claude Fable 5 --- Package@swift-5.9.swift | 70 --------------- Package@swift-6.1.swift | 85 +++++++++++++++++++ Sources/SQLiteKit/Exports.swift | 2 +- .../SQLiteKit/SQLiteConnection+SQLKit.swift | 10 +-- .../SQLiteKit/SQLiteConnectionSource.swift | 8 +- Sources/SQLiteKit/SQLiteDataDecoder.swift | 6 ++ Sources/SQLiteKit/SQLiteDataEncoder.swift | 2 + 7 files changed, 103 insertions(+), 80 deletions(-) delete mode 100644 Package@swift-5.9.swift create mode 100644 Package@swift-6.1.swift diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift deleted file mode 100644 index 4ad1818..0000000 --- a/Package@swift-5.9.swift +++ /dev/null @@ -1,70 +0,0 @@ -// swift-tools-version:5.9 -import class Foundation.ProcessInfo -import PackageDescription - -// Embedded-wasm port (see /Users/scottm/git/c34/khasm/EMBEDDED_PORT_PLAN.md): with -// KHASM_EMBEDDED=1 the SwiftNIO stack and AsyncKit's connection pool are dropped — the -// Embedded build uses sqlite-nio's NIO-free Swift-Concurrency driver directly, gated in -// source with `#if hasFeature(Embedded)` (not available in manifests, hence the env var, -// matching khasm's own manifest gating). Regular builds — including regular WASI via -// NIOAsyncRuntime — keep the NIO stack exactly as on the feat/khasmPAL-2026 base. -let khasmEmbedded = ProcessInfo.processInfo.environment["KHASM_EMBEDDED"] == "1" - -let package = Package( - name: "sqlite-kit", - platforms: [ - .macOS(.v10_15), - .iOS(.v13), - .watchOS(.v6), - .tvOS(.v13), - ], - products: [ - .library(name: "SQLiteKit", targets: ["SQLiteKit"]), - ], - dependencies: [ - // swift-nio / async-kit stay on the PassiveLogic fork URLs (feat/khasmPAL-2026, as on - // the base branch) rather than local clones: the local swift-nio clone carries the - // vestigial Option-1/2 embedded-NIO commits whose os(WASI) gates would change - // regular-WASI NIO behavior, and transitive *path* deps override URL declarations by - // identity in a consuming root graph (khasm). Under KHASM_EMBEDDED=1 both are dropped. - .package(url: "https://github.com/PassiveLogic/swift-nio.git", branch: "feat/khasmPAL-2026"), - .package(url: "https://github.com/PassiveLogic/async-kit.git", branch: "feat/khasmPAL-2026"), - // Local embedded-ported clones (see /Users/scottm/git/c34/EMBEDDED_WASM_NOTES.md), - // branched from the same revisions khasm's Package.resolved pins, so regular - // (non-embedded) builds see identical sources. - .package(path: "../sqlite-nio"), - .package(path: "../sql-kit"), - ], - targets: [ - .target( - name: "SQLiteKit", - dependencies: [ - .product(name: "SQLiteNIO", package: "sqlite-nio"), - .product(name: "SQLKit", package: "sql-kit"), - ] + (khasmEmbedded ? [] : [ - // Dropped on the Embedded build (KHASM_EMBEDDED=1, see note at the top). - .product(name: "NIOFoundationCompat", package: "swift-nio"), - .product(name: "NIOAsyncRuntime", package: "swift-nio", condition: .when(platforms: [.wasi])), - .product(name: "NIOPosix", package: "swift-nio"), - .product(name: "AsyncKit", package: "async-kit"), - ]), - swiftSettings: swiftSettings - ), - .testTarget( - name: "SQLiteKitTests", - dependencies: [ - .product(name: "SQLKitBenchmark", package: "sql-kit"), - .target(name: "SQLiteKit"), - ], - swiftSettings: swiftSettings - ), - ] -) - -var swiftSettings: [SwiftSetting] { [ - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("ConciseMagicFile"), - .enableUpcomingFeature("ForwardTrailingClosures"), - .enableUpcomingFeature("DisableOutwardActorInference"), - .enableExperimentalFeature("StrictConcurrency=complete"), -] } diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift new file mode 100644 index 0000000..338ac93 --- /dev/null +++ b/Package@swift-6.1.swift @@ -0,0 +1,85 @@ +// swift-tools-version:6.1 +import PackageDescription + +let package = Package( + name: "sqlite-kit", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .watchOS(.v6), + .tvOS(.v13), + ], + products: [ + .library(name: "SQLiteKit", targets: ["SQLiteKit"]), + ], + traits: [ + .default(enabledTraits: ["NIO"]), + .trait( + name: "NIO", + description: "Default backend: SwiftNIO + AsyncKit (EventLoopFuture surface and the connection-pool source)." + ), + .trait( + name: "NativeConcurrency", + description: "NIO-free build on Swift concurrency: no AsyncKit connection pool, async-only query surface over sqlite-nio's NativeConcurrency driver. Build with `--traits NativeConcurrency` (replaces the default NIO trait)." + ), + .trait( + name: "Freestanding", + description: "Embedded/freestanding flavor (implies NativeConcurrency). No additional source effect in this package beyond NativeConcurrency; declared so a root's `--traits Freestanding` configuration names a known trait when this package is wired by path.", + enabledTraits: ["NativeConcurrency"] + ), + ], + dependencies: [ + // TODO: SM: Update swift-nio version once NIOAsyncRuntime is available from swift-nio + // .package(url: "https://github.com/apple/swift-nio.git", from: "2.89.0"), + .package(url: "https://github.com/PassiveLogic/swift-nio.git", branch: "feat/khasmPAL-2026"), + + // TODO: SM: Update below once everything is merged and release to the proper repositories +// .package(url: "https://github.com/vapor/sqlite-nio.git", from: "1.9.0"), + .package(url: "https://github.com/PassiveLogic/sqlite-nio.git", branch: "feat/khasmPAL-2026", traits: [ + .trait(name: "default", condition: .when(traits: ["NIO"])), + .trait(name: "NativeConcurrency", condition: .when(traits: ["NativeConcurrency"])), + ]), + .package(url: "https://github.com/vapor/sql-kit.git", from: "3.33.1", traits: [ + .trait(name: "default", condition: .when(traits: ["NIO"])), + .trait(name: "NativeConcurrency", condition: .when(traits: ["NativeConcurrency"])), + ]), +// .package(url: "https://github.com/vapor/async-kit.git", from: "1.19.0"), + .package(url: "https://github.com/PassiveLogic/async-kit.git", branch: "feat/khasmPAL-2026"), + ], + targets: [ + .target( + name: "SQLiteKit", + dependencies: [ + .product(name: "SQLiteNIO", package: "sqlite-nio"), + .product(name: "SQLKit", package: "sql-kit"), + // The SwiftNIO/AsyncKit stack rides the default `NIO` trait; with + // `NativeConcurrency` enabled instead, SQLiteKit is NIO-free (no pool), + // gated in source with `#if NativeConcurrency`. + .product(name: "NIOFoundationCompat", package: "swift-nio", condition: .when(traits: ["NIO"])), + .product(name: "NIOAsyncRuntime", package: "swift-nio", condition: .when(platforms: [.wasi], traits: ["NIO"])), + .product(name: "NIOPosix", package: "swift-nio", condition: .when(traits: ["NIO"])), + .product(name: "AsyncKit", package: "async-kit", condition: .when(traits: ["NIO"])), + ], + swiftSettings: swiftSettings + ), + .testTarget( + name: "SQLiteKitTests", + dependencies: [ + .product(name: "SQLKitBenchmark", package: "sql-kit"), + .target(name: "SQLiteKit"), + ], + swiftSettings: swiftSettings + ), + ] +) + +var swiftSettings: [SwiftSetting] { [ + // This manifest raises the tools-version to 6.1 (for package traits); the package sources + // stay in the Swift 5 language mode of the earlier manifests. + .swiftLanguageMode(.v5), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("ConciseMagicFile"), + .enableUpcomingFeature("ForwardTrailingClosures"), + .enableUpcomingFeature("DisableOutwardActorInference"), + .enableExperimentalFeature("StrictConcurrency=complete"), +] } diff --git a/Sources/SQLiteKit/Exports.swift b/Sources/SQLiteKit/Exports.swift index 2f9e727..c0888dd 100644 --- a/Sources/SQLiteKit/Exports.swift +++ b/Sources/SQLiteKit/Exports.swift @@ -1,6 +1,6 @@ @_documentation(visibility: internal) @_exported import SQLKit @_documentation(visibility: internal) @_exported import SQLiteNIO -#if !hasFeature(Embedded) // AsyncKit (connection pool) is elided on the NIO-free Embedded build. +#if !NativeConcurrency // AsyncKit (connection pool) is elided on the NIO-free NativeConcurrency build. @_documentation(visibility: internal) @_exported import AsyncKit #endif @_documentation(visibility: internal) @_exported import struct Logging.Logger diff --git a/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift b/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift index 132f162..6f949cb 100644 --- a/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift +++ b/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift @@ -178,7 +178,7 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { @usableFromInline let decoder: SQLiteDataDecoder - #if !hasFeature(Embedded) + #if !NativeConcurrency // See `SQLDatabase.eventLoop`. @usableFromInline var eventLoop: any EventLoop { @@ -216,7 +216,7 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { self.queryLogLevel = queryLogLevel } - #if !hasFeature(Embedded) + #if !NativeConcurrency // See `SQLDatabase.execute(sql:_:)`. @usableFromInline func execute( @@ -268,9 +268,9 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { ) } - #if !hasFeature(Embedded) - // `withSession(_:)` is gated out of the embedded `SQLDatabase` protocol (it is a generic requirement - // and relies on the NIO-based `withConnection`, which is concrete-only on WASI). + #if !NativeConcurrency + // `withSession(_:)` rides sqlite-nio's protocol-level `withConnection`, which is concrete-only + // on the NativeConcurrency build (SQLKit's default `withSession` applies there instead). // See `SQLDatabase.withSession(_:)`. @usableFromInline func withSession(_ closure: @escaping @Sendable (any SQLDatabase) async throws -> R) async throws -> R { diff --git a/Sources/SQLiteKit/SQLiteConnectionSource.swift b/Sources/SQLiteKit/SQLiteConnectionSource.swift index bb7c898..2aaf9b7 100644 --- a/Sources/SQLiteKit/SQLiteConnectionSource.swift +++ b/Sources/SQLiteKit/SQLiteConnectionSource.swift @@ -1,7 +1,7 @@ -// The connection-pool source is built on AsyncKit/SwiftNIO/Foundation, none of which are part of -// the NIO-free Embedded build. Embedded uses sqlite-nio's concrete async `SQLiteConnection` +// The connection-pool source is built on AsyncKit/SwiftNIO, which are not part of the NIO-free +// NativeConcurrency build. That build uses sqlite-nio's concrete async `SQLiteConnection` // directly (no pool). -#if !hasFeature(Embedded) +#if !NativeConcurrency #if canImport(Darwin) import Foundation #else @@ -121,4 +121,4 @@ fileprivate extension SQLiteConfiguration.Storage { } } } -#endif // !hasFeature(Embedded) +#endif // !NativeConcurrency diff --git a/Sources/SQLiteKit/SQLiteDataDecoder.swift b/Sources/SQLiteKit/SQLiteDataDecoder.swift index 5489b31..b158cc4 100644 --- a/Sources/SQLiteKit/SQLiteDataDecoder.swift +++ b/Sources/SQLiteKit/SQLiteDataDecoder.swift @@ -15,7 +15,9 @@ public struct SQLiteDataDecoder: Sendable { import Foundation import SQLiteNIO @_spi(CodableUtilities) import SQLKit +#if !NativeConcurrency import NIOFoundationCompat +#endif /// Translates `SQLiteData` values received from the database into `Decodable` values. /// @@ -65,7 +67,11 @@ public struct SQLiteDataDecoder: Sendable { switch data { case .text(let str): buf = .init(str.utf8) + #if NativeConcurrency + case .blob(let blob): buf = .init(blob) + #else case .blob(let blob): buf = .init(buffer: blob, byteTransferStrategy: .noCopy) + #endif // The remaining cases should never happen, but we implement them anyway just in case. case .integer(let n): buf = .init(String(n).utf8) case .float(let n): buf = .init(String(n).utf8) diff --git a/Sources/SQLiteKit/SQLiteDataEncoder.swift b/Sources/SQLiteKit/SQLiteDataEncoder.swift index 938b565..a9a7db9 100644 --- a/Sources/SQLiteKit/SQLiteDataEncoder.swift +++ b/Sources/SQLiteKit/SQLiteDataEncoder.swift @@ -26,7 +26,9 @@ public struct SQLiteDataEncoder: Sendable { } } #else +#if !NativeConcurrency import NIOCore +#endif import Foundation @_spi(CodableUtilities) import SQLKit import SQLiteNIO From 8ea0931bcff6aa830f3b3d141111d5a1a2d8b604 Mon Sep 17 00:00:00 2001 From: Scott Marchant Date: Sat, 11 Jul 2026 10:28:27 -0600 Subject: [PATCH 3/3] build(traits): resolve the trait-forwarded sqlite-nio/sql-kit deps from the fork's feat/native-concurrency-trait branches The trait-conditional dependency declarations enable NativeConcurrency on sqlite-nio and sql-kit, so the resolved revisions must declare that trait. Point both at the PassiveLogic fork branches carrying the trait manifests (sql-kit's upstream 3.33.1+ and the fork's feat/khasmPAL-2026 sqlite-nio branch declare no traits, which fails resolution under --traits). Co-Authored-By: Claude Fable 5 --- Package@swift-6.1.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Package@swift-6.1.swift b/Package@swift-6.1.swift index 338ac93..e0c2903 100644 --- a/Package@swift-6.1.swift +++ b/Package@swift-6.1.swift @@ -35,11 +35,12 @@ let package = Package( // TODO: SM: Update below once everything is merged and release to the proper repositories // .package(url: "https://github.com/vapor/sqlite-nio.git", from: "1.9.0"), - .package(url: "https://github.com/PassiveLogic/sqlite-nio.git", branch: "feat/khasmPAL-2026", traits: [ + .package(url: "https://github.com/PassiveLogic/sqlite-nio.git", branch: "feat/native-concurrency-trait", traits: [ .trait(name: "default", condition: .when(traits: ["NIO"])), .trait(name: "NativeConcurrency", condition: .when(traits: ["NativeConcurrency"])), ]), - .package(url: "https://github.com/vapor/sql-kit.git", from: "3.33.1", traits: [ +// .package(url: "https://github.com/vapor/sql-kit.git", from: "3.33.1"), + .package(url: "https://github.com/PassiveLogic/sql-kit.git", branch: "feat/native-concurrency-trait", traits: [ .trait(name: "default", condition: .when(traits: ["NIO"])), .trait(name: "NativeConcurrency", condition: .when(traits: ["NativeConcurrency"])), ]),