SQLiteData
Type-safe SQLite persistence using SQLiteData by Point-Free. A fast, lightweight replacement for SwiftData with CloudKit synchronization, built on GRDB and StructuredQueries.
When to Use
Use this skill when you're:
- Defining
@Tablemodels - Writing queries with
@FetchAlland@FetchOne - Inserting, updating, upserting, or deleting records
- Batch importing data
- Setting up CloudKit sync or record sharing with SQLiteData
- Fixing StructuredQueries compiler errors, such as
==in a predicate or a missing#bind - Testing sync or previewing sharing UI
Core principle Value types (struct), the @Table macro, and database.write { } blocks for every mutation.
Choose SQLiteData for type-safe SQLite, CloudKit sync with record sharing, large datasets (50,000+ records), and Swift 6 strict concurrency. Use SwiftData instead for simple CRUD with @Model classes when you don't need CloudKit record sharing, and raw GRDB for joins across four or more tables, window functions, or hand-written SQL on a profiled hot path.
Example Prompts
Questions you can ask your assistant that will draw from this skill:
- "How do I set up a
@Tablemodel with custom column types?" - "Why does
.where { $0.status == .done }no longer compile?" - "Why can't I
awaitinside adatabase.writeblock?" - "How do I do a bulk insert efficiently?"
- "How do I set up CloudKit sync with SQLiteData?"
- "What's the difference between
@FetchAlland@FetchOne?" - "How do I group my list into sections the way SwiftData does, but in SQL?"
- "My
@FetchOneshows the wrong row — I passed the record, but it loads a different one." - "Which SQLiteData package traits should I turn on?"
- "My tests pass, but sync is never exercised. What am I missing?"
What This Skill Provides
Table Models
- The property named
idbecomes the primary key; a key on any other column needs@Column(primaryKey: true) - Properties with defaults map to non-nullable columns, and optionals to nullable ones
@Column(as:)for custom representations@Ephemeralfor properties that aren't persisted
Queries
@FetchAllfor arrays, and@FetchOnefor single values and aggregates- Why single-record observation must be seeded with a statement (
Item.find(id)), not a record wherewith key paths and closures,order(by:),limit, andoffset- Static helpers
fetchAllandfind(v1.4.0+) - Lifecycle-aware fetching that cancels with the view's
.task(v1.4.0+) - Sectioned results with
sectionBy:(v1.8.0+), throughResultsSectionCollectionandResultsSection - Loading and error state (
isLoading,loadError) inherited from swift-sharing
Mutations
- Inserts with the
Draftpattern, including lazily initialized columns RETURNINGto get generated values back- Single and bulk updates and deletes
- Upserts, with merge strategies and partial unique indexes
- Batch inserts and transaction safety
CloudKit Sync
SyncEnginesetup throughprepareDependencies- Manual sync control (v1.3.0+) and sync-state observation (v1.2.0+)
- Querying sync metadata (v1.3.0+)
- Record sharing, with per-record database resolution (v1.11.1+)
- Previewable sharing UI:
CloudSharingViewshows a mock in previews and tests (v1.5.0+) SyncEngine.migratePrimaryKeysfor switching sync strategies
Package Traits
- Six traits — CasePaths, Tagged, ColumnCoding, LazyInitializableByDefault, StrictDecoding, SuppressPlatformSQLiteAvailability — plus
SQLiteDataTagged, a deprecated alias forTagged - The three that upstream documents as becoming default behavior in the next major release
Testing and Previews
defaultDatabase(), which provisions a temporary on-disk pool for both previews and tests- The sync engine no longer auto-starts under test (v1.5.1+), so a test that exercises sync must call
syncEngine.start()
Raw SQL
- The
#sqlmacro for raw queries and custom expressions
Key Pattern
Basic CRUD
// MODEL
@Table nonisolated struct Item: Identifiable {
let id: UUID // Property named `id` = auto primary key
var title = "" // Default = non-nullable
var isActive = true
var notes: String? // Optional = nullable
@Ephemeral var isSelected = false // Not persisted
}
// SETUP
prepareDependencies { $0.defaultDatabase = try! appDatabase() }
@Dependency(\.defaultDatabase) var database
// FETCH
@FetchAll var items: [Item]
@FetchAll(Item.order(by: \.title).where(\.isActive)) var activeItems
// INSERT
try database.write { db in
try Item.insert { Item.Draft(title: "New") }.execute(db)
}
// UPDATE
try database.write { db in
try Item.find(id).update { $0.title = #bind("Updated") }.execute(db)
}
// DELETE
try database.write { db in
try Item.find(id).delete().execute(db)
}Common Queries
// Filter
Item.where(\.isActive) // Keypath (simple)
Item.where { $0.title.like("%phone%") } // Closure (complex)
Item.where { $0.status.eq(#bind(.done)) } // Enum comparison
// Sort and paginate
Item.order(by: \.title) // Ascending
Item.order { $0.createdAt.desc() } // Descending
Item.limit(10).offset(20) // PaginationDocumentation Scope
This page documents the sqlitedata skill (in the axiom-data suite) — the SQLiteData patterns your assistant uses when you work with Point-Free's SQLite library.
Related
- sqlitedata-ref – advanced patterns: CTEs, views, custom aggregates,
@Selection, FTS5, and@DatabaseFunction - sqlitedata-migration – moving an app from SwiftData to SQLiteData
- grdb – raw GRDB for queries the query builder can't express
- database-migration – safe schema evolution patterns
- swift-sharing – the layer
@FetchAlland@FetchOneare built on;isLoading,loadError, and custom persistence strategies live there
Resources
GitHub: pointfreeco/sqlite-data, pointfreeco/swift-structured-queries, groue/GRDB.swift