Skip to content

WASI support: build SQLiteKit without SwiftNIO or AsyncKit - #4

Draft
scottmarchant wants to merge 3 commits into
base/vapor-mainfrom
feat/wasi-nio-free
Draft

WASI support: build SQLiteKit without SwiftNIO or AsyncKit#4
scottmarchant wants to merge 3 commits into
base/vapor-mainfrom
feat/wasi-nio-free

Conversation

@scottmarchant

@scottmarchant scottmarchant commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Depends on PassiveLogic/sqlite-nio#4 and PassiveLogic/sql-kit#3 being merged and tagged upstream before the WASI CI lane can pass. The released siblings have no WASI support (sqlite-nio's current release, 1.13.0, predates its WASI PR), so the version floors here will need bumping to the first releases that include it once those exist. Stays in draft until then. The native build and tests already pass against the released siblings, so the ordering only matters for the WASI lane.

Builds SQLiteKit on wasm32-unknown-wasip1 without SwiftNIO or AsyncKit. Embedded Swift is stacked separately as #5.

Changes:

  • SQLiteConnectionSource and the AsyncKit re-export gate on canImport(AsyncKit), so there is no connection pool on WASI. That is a real functional loss worth being upfront about, and a tolerable one: WASI preview 1 is single-threaded, so a pool buys nothing there, and callers use sqlite-nio's concrete async SQLiteConnection directly.
  • The SQLDatabase wrapper's eventLoop, its EventLoopFuture execute overload, and withSession(_:) gate on canImport(NIOCore), matching what SQLKit does to the corresponding protocol requirements.
  • SQLiteDataDecoder's JSON fallback reads the blob as [UInt8] where NIOFoundationCompat is absent, matching SQLiteData's representation there.
  • Both manifests (Package.swift at tools 5.8 and the existing Package@swift-5.9.swift) get the same .when(platforms: nonWASIPlatforms) hunk, so 5.9+ toolchains are not silently left behind. Same idiom as Add support for WASILibc apple/swift-nio#2671.
  • CI passes with_wasm: true to vapor/ci (sqlite-kit previously had no with: block at all).

On AsyncKit: upstream vapor/async-kit does not compile for WASI, so it is elided rather than ported, which removes vapor/async-kit#111 as a prerequisite entirely. If async-kit gains WASI support later, the condition relaxes and the pool comes back.

Verification:

  • Native swift build and swift test: 9 tests green against the released siblings, which now resolve sqlite-nio at 1.13.0. The CI-flags build is warning-free against 1.13.0; the two Sendable warnings previously noted here came from the older released sqlite-nio and are gone. swift package diagnose-api-breaking-changes reports no breaking changes.
  • WASI (--swift-sdk swift-6.3.1-RELEASE_wasm), with the sibling WASI branches wired in via swift package edit --path (the committed manifests keep the upstream vapor/... URLs and floors): green, zero SwiftNIO or AsyncKit object files, no NIO, AsyncKit, EventLoop, or ByteBuffer symbols. The wasm check skips drafts and will stay red until the dependency PRs land and the floors are bumped, which is the sequencing note at the top.

Notes for review: no SwiftPM traits, no manifest added, no tools-version bump, no new dependencies.

Base base/vapor-main is an exact snapshot of vapor/sqlite-kit@main at 492c46e, excluding the fork's one extra commit (the NIOAsyncRuntime approach from #3, superseded here; apple/swift-nio#3487 was closed unmerged and a pre-1.0 dependency is not acceptable upstream). Rebasing onto the real vapor/main is a no-op.

Motivation:

Neither SwiftNIO nor AsyncKit can be built for `wasm32-unknown-wasip1`: NIOPosix
is built on POSIX sockets and threads, neither of which WASI preview 1 provides,
and AsyncKit's connection pool is built on NIOPosix in turn. SQLiteKit uses them
for the connection pool and for the legacy `EventLoopFuture` half of an API whose
`async` half is already the recommended one, so it can build without them, given
somewhere to put the differences.

Modifications:

Gate the affected declarations with `#if canImport(...)`, keyed on the module that
actually supplies each API rather than on a platform, so the sources stay in step
with whatever the manifest resolves for the target being built and with the gates
sqlite-nio and sql-kit apply to the same dependencies.

What drops out where the modules are absent:

- `SQLiteConnectionSource` — a `ConnectionPoolSource` over AsyncKit and
  `NIOThreadPool` — drops out entirely, along with the `AsyncKit` re-export.
  Callers use sqlite-nio's concrete `async` `SQLiteConnection` directly; there is
  no connection pool on those platforms.
- The `SQLDatabase` wrapper's `eventLoop`, its
  `execute(sql:_:) -> EventLoopFuture<Void>` overload, and `withSession(_:)`
  (which rides sqlite-nio's protocol-level `withConnection`) drop out. SQLKit
  removes the first two as protocol requirements on the same platforms and
  supplies a default for the third.
- `SQLiteDataDecoder`'s JSON fallback reads a `[UInt8]` blob rather than a
  `ByteBuffer`, matching `SQLiteData`'s representation there.

The decoder's two blob cases stay forked rather than collapsing to the `[UInt8]`
form on both: `Data.init(buffer:byteTransferStrategy:)` is a zero-copy bridge
with no `[UInt8]` equivalent, so collapsing them would silently add a copy where
SwiftNIO is present. The gate names NIOFoundationCompat, which is the module that
declares that initializer.

`SQLiteDataEncoder`'s `import NIOCore` is dropped rather than gated: the file
references no NIOCore symbol, so the import was already dead.

Result:

Where SwiftNIO and AsyncKit are available the public API and the symbol graph are
unchanged — the same symbols with the same `docComment` line counts — and
`diagnose-api-breaking-changes` reports no differences. Where they are absent
SQLiteKit compiles down to the pool-free, `async`-only surface.
Motivation:

Neither SwiftNIO nor AsyncKit builds for `wasm32-unknown-wasip1`, so SQLiteKit
currently fails to configure for that platform at all — the failure is in
dependency resolution, before any of the conditional sources in the previous
commit get a chance to matter.

Modifications:

Gate the NIOFoundationCompat and AsyncKit products on
`.when(platforms: nonWASIPlatforms)` in both manifests. Target dependency
conditions are evaluated per platform, so on WASI the products are simply not
linked and the `canImport(...)` gates select the pool-free `async` API.

`.when(platforms:)` can only include, never exclude, so excluding one platform
means enumerating the others; each list is the set the manifest's own tools
version knows about, noted as such so it is not extended without also raising
that version.

Result:

On every other platform the resolved dependency set is byte-identical to before.
On WASI the build graph contains no SwiftNIO and no AsyncKit module.
Motivation:

`vapor/ci`'s reusable unit-test workflow already knows how to build a package for
`wasm32-unknown-wasip1`; sqlite-kit simply had not opted in, so nothing stops the
SwiftNIO-free configuration from regressing unnoticed.

Modifications:

Pass `with_wasm: true` to the reusable workflow, as sql-kit already does.

Result:

The WASI build is checked on every pull request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant