Swift Getting Started
Swift Getting Started
This guide sets up SQLiteNow in a SwiftPM or Xcode app using the released SQLiteNow SwiftPM package. You will keep SQL files in your app repository, generate a local Swift package, add that generated package to the app, and use the generated typed API from Swift.
Replace X.Y.Z with the latest SQLiteNow release version. SwiftPM dependency
versions use the bare version, such as X.Y.Z; GitHub release asset URLs use
the vX.Y.Z tag.
Requirements
- macOS with Xcode command line tools
- SwiftPM with
swift-tools-version: 6.0support - Java 17 or newer on
PATH
Check Java with:
java -version
Add SQLiteNow
Add the released SQLiteNow package to the Swift package that owns your SQL files:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MyAppleApp",
platforms: [
.iOS(.v15),
.macOS(.v14),
],
dependencies: [
.package(url: "https://github.com/mobiletoly/sqlitenow-kmp.git", from: "X.Y.Z"),
],
targets: [
.target(name: "MyAppleApp"),
]
)
The dependency makes the sqlitenow-generate command plugin available to
swift package plugin. When the package dependency is added to an Xcode
project, SQLiteNow also appears in Xcode’s package plugin UI.
Add SQL Files
Place SQL files under a database directory in the Swift package:
SQLiteNow/databases/AppDatabase/
schema/person.sql
init/
migration/
queries/person/insert.sql
queries/person/selectAll.sql
SQLiteNow uses the same SQL folder names for Swift, Kotlin Multiplatform, and Flutter/Dart:
schema: schema objects such as tables, indexes, and viewsinit: data or setup SQL for a fresh databasemigration: versioned migration SQLqueries: SQL files that generate typed Swift APIs
A minimal schema and query set can look like this:
-- SQLiteNow/databases/AppDatabase/schema/person.sql
CREATE TABLE person (
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL
);
-- SQLiteNow/databases/AppDatabase/queries/person/insert.sql
INSERT INTO person (id, name)
VALUES (:id, :name);
-- SQLiteNow/databases/AppDatabase/queries/person/selectAll.sql
SELECT *
FROM person
ORDER BY id;
Configure SQLiteNow
Create SQLiteNow.json at the Swift package root:
{
"schemaVersion": 1,
"databases": [
{
"databaseName": "AppDatabase",
"swiftPackageName": "AppDatabaseSQLiteNow",
"swiftTargetName": "AppDatabaseSQLiteNow",
"runtime": "core"
}
]
}
The released SQLiteNow SwiftPM package supplies the matching runtime artifact URL and checksum for its version. App configs only choose the runtime mode.
When sqlDirectory is omitted, SQLiteNow reads SQL from
SQLiteNow/databases/<databaseName> under the package root. When
outputDirectory is omitted, it writes the generated package to:
SQLiteNowGenerated/AppDatabaseSQLiteNow
Generate The Package
Run generation from the Swift package root:
swift package plugin --allow-writing-to-package-directory sqlitenow-generate
From Xcode, invoke the sqlitenow-generate package plugin command from the
package plugin UI after adding the SQLiteNow package dependency.
Xcode uses the Xcode project directory as the default root. If the Swift package
root is above the .xcodeproj, pass the package root and config path together:
--package-root /path/to/MyAppleApp --config Config/SQLiteNow.json
The same options can be passed from SwiftPM:
swift package plugin --allow-writing-to-package-directory sqlitenow-generate --package-root /path/to/MyAppleApp --config Config/SQLiteNow.json
First-run setup matters: generate the local package before adding it as an app dependency, or commit previously generated output that matches the SQL sources.
Add The Generated Package
For a SwiftPM app, add the generated package path as a local dependency and link the generated product:
dependencies: [
.package(url: "https://github.com/mobiletoly/sqlitenow-kmp.git", from: "X.Y.Z"),
.package(path: "SQLiteNowGenerated/AppDatabaseSQLiteNow"),
],
targets: [
.executableTarget(
name: "MyAppleApp",
dependencies: [
.product(name: "AppDatabaseSQLiteNow", package: "AppDatabaseSQLiteNow"),
]
),
]
For an Xcode app, run generation first so the package directory contains
Package.swift, then add SQLiteNowGenerated/AppDatabaseSQLiteNow as a local
package dependency and link the AppDatabaseSQLiteNow product to the app
target.
App source imports the generated product:
import AppDatabaseSQLiteNow
Open The Database
Create the parent directory before opening a file-backed database:
import AppDatabaseSQLiteNow
import Foundation
let supportURL = FileManager.default
.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
try FileManager.default.createDirectory(
at: supportURL,
withIntermediateDirectories: true
)
let databaseURL = supportURL.appendingPathComponent("app.sqlite")
let db = AppDatabase(path: databaseURL)
try await db.open()
Close the database when the owning app object shuts down:
try await db.close()
Insert And Query Rows
Generated execute methods are async throws. SELECT statements return typed
runners with list(), one(), oneOrNull(), and stream().
try await db.person.insert(PersonInsertParams(id: 1, name: "Ada"))
let people: [PersonRow] = try await db.person.selectAll().list()
Watch Query Results
for try await rows in db.person.selectAll().stream() {
print("Person count: \(rows.count)")
}
Generated write methods report invalidation through SQLiteNow’s runtime, so the stream emits again after generated inserts, updates, and deletes touch the query’s tables.
Use A Transaction
Transactions use a synchronous mutation builder for generated non-returning execute statements:
try await db.transaction { tx in
tx.person.insert(PersonInsertParams(id: 2, name: "Grace"))
}
If any operation in the transaction fails, SQLiteNow rolls back the batch and throws a Swift error.
Regenerate Safely
The generated package is disposable output. Change SQL files or
SQLiteNow.json, then rerun sqlitenow-generate. Do not hand-edit files under
SQLiteNowGenerated/; edits there are replaced by the next generation run.
For local development, rerun generation whenever SQL changes. For CI, choose one of these project policies:
- Commit
SQLiteNowGenerated/and fail CI when generated output is stale. - Run
sqlitenow-generatebefore building the app target.
The generator writes request files under .build/sqlitenow/requests/ and
records package metadata under .sqlitenow/package-manifest.json inside the
generated package.