Token导航 LogoToken导航

Agent Skill · 研究检索

axiom-sqlitedata(公理 SQLite 数据)

用于辅助数据库表结构、查询语句、迁移脚本和数据维护任务。它适合让 Agent 分析 schema、编写 SQL、排查查询问题、整理索引或生成迁移建议。使用时需要明确数据库类型、连接环境和目标表,区分只读分析与写入变更;涉及删除、更新、迁移和批量导入时,应优先 dry-run、备份或事务保护,避免误操作。

axiom-sqlitedata 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 21,602。

总安装 4,822周安装 205GitHub Stars 873下载 1,689核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-sqlitedata(公理 SQLite 数据)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-sqlitedata
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-sqlitedata
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-sqlitedata

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

用于 SQLiteData 数据库操作,支持 CloudKit 同步和结构化查询。适用于 iOS 17+ 项目中的持久化存储、关系管理和批量数据操作。提供 @Table 宏定义、事务写块和类型安全查询等核心能力。安装前建议确认权限范围和维护状态,避免触发联网或文件读写操作。

axiom-sqlitedata 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/axiom-sqlitedata

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-sqlitedata;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

axiom-sqlitedata 怎么安装?

在终端执行 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-sqlitedata。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

axiom-sqlitedata 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

axiom-sqlitedata 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

axiom-sqlitedata 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

axiom-sqlitedata 用的人多吗?

公开数据是累计安装 4,822 次,近一周安装 205 次,下载 1,689 次,GitHub 873 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

axiom-sqlitedata 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
axiom-sqlitedata

公理 SQLite 数据 · 本页

4,822873需要联网MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

SQLiteData

Overview

Type-safe SQLite persistence using SQLiteData (pointfreeco/sqlite-data) by Point-Free. A fast, lightweight replacement for SwiftData with CloudKit synchronization support, built on GRDB (groue/GRDB.swift) and StructuredQueries (pointfreeco/swift-structured-queries).

Core principle: Value types (struct) + @Table macro + database.write {} blocks for all mutations.

For advanced patterns (CTEs, views, custom aggregates, schema composition), see the axiom-sqlitedata-ref reference skill.

Requires: iOS 17+, Swift 6 strict concurrency License: MIT

When to Use SQLiteData

Choose SQLiteData when you need:

  • Type-safe SQLite with compiler-checked queries
  • CloudKit sync with record sharing
  • Large datasets (50k+ records) with near-raw-SQLite performance
  • Value types (structs) instead of classes
  • Swift 6 strict concurrency support

Use SwiftData instead when:

  • Simple CRUD with native Apple integration
  • Prefer @Model classes over structs
  • Don't need CloudKit record sharing

Use raw GRDB when:

  • Complex SQL joins across 4+ tables
  • Custom migration logic beyond schema changes
  • Performance-critical operations needing manual SQL

Quick Reference

// MODEL
@Table nonisolated struct Item: Identifiable {
    let id: UUID                    // First let = auto primary key
    var title = ""                  // Default = non-nullable
    var notes: String?              // Optional = nullable
    @Column(as: Color.Hex.self)
    var color: Color = .blue        // Custom representation
    @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(\.isInStock)) var items
@FetchOne(Item.count()) var count = 0

// FETCH (static helpers - v1.4.0+)
try Item.fetchAll(db)              // vs Item.all.fetchAll(db)
try Item.find(db, key: id)         // returns non-optional Item

// INSERT
try database.write { db in
    try Item.insert { Item.Draft(title: "New") }.execute(db)
}

// UPDATE (single)
try database.write { db in
    try Item.find(id).update { $0.title = #bind("Updated") }.execute(db)
}

// UPDATE (bulk)
try database.write { db in
    try Item.where(\.isInStock).update { $0.notes = #bind("") }.execute(db)
}

// DELETE
try database.write { db in
    try Item.find(id).delete().execute(db)
    try Item.where { $0.id.in(ids) }.delete().execute(db)  // bulk
}

// QUERY
Item.where(\.isActive)                     // Keypath (simple)
Item.where { $0.title.contains("phone") }  // Closure (complex)
Item.where { $0.status.eq(#bind(.done)) }  // Enum comparison
Item.order(by: \.title)                    // Sort
Item.order { $0.createdAt.desc() }         // Sort descending
Item.limit(10).offset(20)                  // Pagination

// RAW SQL (#sql macro)
#sql("SELECT * FROM items WHERE price > 100")  // Type-safe raw SQL
#sql("coalesce(date(\(dueDate)) = date(\(now)), 0)")  // Custom expressions

// CLOUDKIT (v1.2-1.4+)
prepareDependencies {
    $0.defaultSyncEngine = try SyncEngine(
        for: $0.defaultDatabase,
        tables: Item.self
    )
}
@Dependency(\.defaultSyncEngine) var syncEngine

// Manual sync control (v1.3.0+)
try await syncEngine.fetchChanges()  // Pull from CloudKit
try await syncEngine.sendChanges()   // Push to CloudKit
try await syncEngine.syncChanges()   // Bidirectional

// Sync state observation (v1.2.0+)
syncEngine.isSendingChanges    // true during upload
syncEngine.isFetchingChanges   // true during download
syncEngine.isSynchronizing     // either sending or fetching

Anti-Patterns (Common Mistakes)

❌ Using == in predicates

// WRONG — removed in StructuredQueries 0.31+ (compiler error)
.where { $0.status == .completed }

// CORRECT — use comparison methods
.where { $0.status.eq(#bind(.completed)) }

❌ Missing #bind in update assignments (StructuredQueries 0.31+)

// WRONG — compiler error in StructuredQueries 0.31+
Item.find(id).update { $0.title = "New" }.execute(db)

// CORRECT — wrap literal values with #bind
Item.find(id).update { $0.title = #bind("New") }.execute(db)

// NOTE: Compound operators (+=, -=) don't need #bind — they auto-bind
Item.find(id).update { $0.title += "!" }.execute(db)  // OK

❌ Wrong update order

// WRONG — .update before .where
Item.update { $0.title = #bind("X") }.where { $0.id.eq(#bind(id)) }

// CORRECT — .find() for single, .where() before .update() for bulk
Item.find(id).update { $0.title = #bind("X") }.execute(db)
Item.where(\.isOld).update { $0.archived = #bind(true) }.execute(db)

❌ Instance methods for insert

// WRONG — no instance insert method
let item = Item(id: UUID(), title: "Test")
try item.insert(db)

// CORRECT — static insert with .Draft
try Item.insert { Item.Draft(title: "Test") }.execute(db)

❌ Missing nonisolated

// WRONG — Swift 6 concurrency warning
@Table struct Item { ... }

// CORRECT
@Table nonisolated struct Item { ... }

❌ Awaiting inside write block

// WRONG — write block is synchronous
try await database.write { db in ... }

// CORRECT — no await inside the block
try database.write { db in
    try Item.insert { ... }.execute(db)
}

❌ Forgetting .execute(db)

// WRONG — builds query but doesn't run it
try database.write { db in
    Item.insert { Item.Draft(title: "X") }  // Does nothing!
}

// CORRECT
try database.write { db in
    try Item.insert { Item.Draft(title: "X") }.execute(db)
}

@Table Model Definitions

Basic Table

import SQLiteData

@Table
nonisolated struct Item: Identifiable {
    let id: UUID           // First `let` = auto primary key
    var title = ""
    var isInStock = true
    var notes = ""
}

Key patterns:

  • Use struct, not class (value types)
  • Add nonisolated for Swift 6 concurrency
  • First let property is automatically the primary key
  • Use defaults (= "", = true) for non-nullable columns
  • Optional properties (String?) map to nullable SQL columns

Custom Primary Key

@Table
nonisolated struct Tag: Hashable, Identifiable {
    @Column(primaryKey: true)
    var title: String      // Custom primary key
    var id: String { title }
}

Column Customization

@Table
nonisolated struct RemindersList: Hashable, Identifiable {
    let id: UUID

    @Column(as: Color.HexRepresentation.self)  // Custom type representation
    var color: Color = .blue

    var position = 0
    var title = ""
}

Foreign Keys

@Table
nonisolated struct Reminder: Hashable, Identifiable {
    let id: UUID
    var title = ""
    var remindersListID: RemindersList.ID  // Foreign key (explicit column)
}

@Table
nonisolated struct Attendee: Hashable, Identifiable {
    let id: UUID
    var name = ""
    var syncUpID: SyncUp.ID  // References parent
}

Note: SQLiteData uses explicit foreign key columns. Relationships are expressed through joins, not @Relationship macros.

Querying Related Tables (Joins)

Don't fetch all records and filter in Swift — push filtering to the database:

// ❌ Anti-pattern: Fetch all, filter in Swift
let allReminders = try database.read { try Reminder.all.fetch($0) }
let filtered = allReminders.filter { $0.remindersListID == listID }

// ✅ Filter at database level
let filtered = try database.read {
    try Reminder.all
        .filter { $0.remindersListID.eq(#bind(listID)) }
        .fetch($0)
}

// ✅ Join across tables with filtering
let remindersWithList = try database.read {
    try Reminder.all
        .join(RemindersList.all) { $0.remindersListID.eq($1.id) }
        .filter { $1.name.eq(#bind("Shopping")) }
        .fetch($0)
}

// ✅ Left join (include reminders even if no list)
let allWithOptionalList = try database.read {
    try Reminder.all
        .leftJoin(RemindersList.all) { $0.remindersListID.eq($1.id) }
        .fetch($0)
}

For complex joins across 4+ tables, drop down to raw GRDB (see axiom-grdb).

@Ephemeral — Non-Persisted Properties

Mark properties that exist in Swift but not in the database:

@Table
nonisolated struct Item: Identifiable {
    let id: UUID
    var title = ""
    var price: Decimal = 0

    @Ephemeral
    var isSelected = false  // Not stored in database

    @Ephemeral
    var formattedPrice: String {  // Computed, not stored
        "$\(price)"
    }
}

Use cases:

  • UI state (selection, expansion, hover)
  • Computed properties derived from stored columns
  • Transient flags for business logic
  • Default values for properties not yet in schema

Important: @Ephemeral properties must have default values since they won't be populated from the database.


Database Setup

Create Database

import Dependencies
import SQLiteData
import GRDB

func appDatabase() throws -> any DatabaseWriter {
    var configuration = Configuration()
    configuration.prepareDatabase { db in
        // Configure database behavior
        db.trace { print("SQL: \($0)") }  // Optional SQL logging
    }

    let database = try DatabaseQueue(configuration: configuration)

    var migrator = DatabaseMigrator()

    // Register migrations
    migrator.registerMigration("v1") { db in
        try #sql(
            """
            CREATE TABLE "items" (
                "id" TEXT PRIMARY KEY NOT NULL DEFAULT (uuid()),
                "title" TEXT NOT NULL DEFAULT '',
                "isInStock" INTEGER NOT NULL DEFAULT 1,
                "notes" TEXT NOT NULL DEFAULT ''
            ) STRICT
            """
        )
        .execute(db)
    }

    try migrator.migrate(database)
    return database
}

Register in Dependencies

extension DependencyValues {
    var defaultDatabase: any DatabaseWriter {
        get { self[DefaultDatabaseKey.self] }
        set { self[DefaultDatabaseKey.self] = newValue }
    }
}

private enum DefaultDatabaseKey: DependencyKey {
    static let liveValue: any DatabaseWriter = {
        try! appDatabase()
    }()
}

// In app init or @main
prepareDependencies {
    $0.defaultDatabase = try! appDatabase()
}

Query Patterns

Property Wrappers (@FetchAll, @FetchOne)

The primary way to observe database changes in SwiftUI:

struct ItemsList: View {
    @FetchAll(Item.order(by: \.title)) var items

    var body: some View {
        List(items) { item in
            Text(item.title)
        }
    }
}

Key behaviors:

  • Automatically subscribes to database changes
  • Updates when any Item changes
  • Runs on the main thread
  • Cancels observation when view disappears (iOS 17+)

@FetchOne for Aggregates

struct StatsView: View {
    @FetchOne(Item.count()) var totalCount = 0
    @FetchOne(Item.where(\.isInStock).count()) var inStockCount = 0

    var body: some View {
        Text("Total: \(totalCount), In Stock: \(inStockCount)")
    }
}

Lifecycle-Aware Fetching (v1.4.0+)

Use .task to automatically cancel observation when view disappears:

struct ItemsList: View {
    @Fetch(Item.all, animation: .default)
    private var items = [Item]()

    @State var searchQuery = ""

    var body: some View {
        List(items) { item in
            Text(item.title)
        }
        .searchable(text: $searchQuery)
        .task(id: searchQuery) {
            // Automatically cancels when view disappears or searchQuery changes
            try? await $items.load(
                Item.where { $0.title.contains(searchQuery) }
                    .order(by: \.title)
            ).task  // ← .task for auto-cancellation
        }
    }
}

Before v1.4.0 (manual cleanup):

.task {
    try? await $items.load(query)
}
.onDisappear {
    Task { try await $items.load(Item.none) }
}

With v1.4.0 (automatic):

.task {
    try? await $items.load(query).task  // Auto-cancels
}

Filtering

// Simple keypath filter
let active = Item.where(\.isActive)

// Complex closure filter
let recent = Item.where { $0.createdAt > lastWeek && !$0.isArchived }

// Contains/prefix/suffix
let matches = Item.where { $0.title.contains("phone") }
let starts = Item.where { $0.title.hasPrefix("iPhone") }

Sorting

// Single column
let sorted = Item.order(by: \.title)

// Descending
let descending = Item.order { $0.createdAt.desc() }

// Multiple columns
let multiSort = Item.order { ($0.priority, $0.createdAt.desc()) }

Static Fetch Helpers (v1.4.0+)

Cleaner syntax for fetching:

// OLD (verbose)
let items = try Item.all.fetchAll(db)
let item = try Item.find(id).fetchOne(db)  // returns Optional<Item>

// NEW (concise)
let items = try Item.fetchAll(db)
let item = try Item.find(db, key: id)      // returns Item (non-optional)

// Works with where clauses too
let active = try Item.where(\.isActive).find(db, key: id)

Key improvement: .find(db, key:) returns non-optional, throwing an error if not found.


Insert / Update / Delete

Insert

try database.write { db in
    try Item.insert {
        Item.Draft(title: "New Item", isInStock: true)
    }
    .execute(db)
}

Insert with RETURNING (get generated ID)

let newId = try database.write { db in
    try Item.insert {
        Item.Draft(title: "New Item")
    }
    .returning(\.id)
    .fetchOne(db)
}

Update Single Record

try database.write { db in
    try Item.find(itemId)
        .update { $0.title = #bind("Updated Title") }
        .execute(db)
}

Update Multiple Records

try database.write { db in
    try Item.where(\.isArchived)
        .update { $0.isDeleted = #bind(true) }
        .execute(db)
}

Delete

// Delete single
try database.write { db in
    try Item.find(id).delete().execute(db)
}

// Delete multiple
try database.write { db in
    try Item.where { $0.createdAt < cutoffDate }
        .delete()
        .execute(db)
}

Upsert (Insert or Update)

SQLite's UPSERT (INSERT... ON CONFLICT... DO UPDATE) expresses "insert if missing, otherwise update" in one statement.

try database.write { db in
    try Item.insert {
        item
    } onConflict: { cols in
        (cols.libraryID, cols.remoteID)   // Conflict target columns
    } doUpdate: { row, excluded in
        row.name = excluded.name           // Merge semantics
        row.notes = excluded.notes
    }
    .execute(db)
}
Parameters
  • onConflict: — Columns defining "same row" (must match UNIQUE constraint/index)
  • doUpdate: — What to update on conflict

- row = existing database row - excluded = proposed insert values (SQLite's excluded table)

With Partial Unique Index

When your UNIQUE index has a WHERE clause, add a conflict filter:

try Item.insert {
    item
} onConflict: { cols in
    (cols.libraryID, cols.remoteID)
} where: { cols in
    cols.remoteID.isNot(nil)          // Match partial index condition
} doUpdate: { row, excluded in
    row.name = excluded.name
}
.execute(db)
Schema Requirement
CREATE UNIQUE INDEX idx_items_sync_identity
ON items (libraryID, remoteID)
WHERE remoteID IS NOT NULL
Merge Strategies
Replace All Mutable Fields (Sync Mirror)
doUpdate: { row, excluded in
    row.name = excluded.name
    row.notes = excluded.notes
    row.updatedAt = excluded.updatedAt
}
Merge Without Clobbering
doUpdate: { row, excluded in
    row.name = excluded.name.ifnull(row.name)
    row.notes = excluded.notes.ifnull(row.notes)
}
Last-Write-Wins (Raw SQL)
try db.execute(sql: """
    INSERT INTO items (id, name, updatedAt) VALUES (?, ?, ?)
    ON CONFLICT(id) DO UPDATE SET
        name = excluded.name,
        updatedAt = excluded.updatedAt
    WHERE excluded.updatedAt >= items.updatedAt
    """, arguments: [item.id, item.name, item.updatedAt])
// Use >= to handle timestamp ties (last arrival wins)
❌ Common Upsert Mistakes
Missing UNIQUE Constraint
// WRONG — no index to conflict against
onConflict: { ($0.libraryID, $0.remoteID) }
// but table has no UNIQUE(libraryID, remoteID)
Using INSERT OR REPLACE
// WRONG — REPLACE deletes then inserts, breaking FK relationships
try db.execute(sql: "INSERT OR REPLACE INTO items ...")

// CORRECT — use ON CONFLICT for true upsert
try Item.insert { ... } onConflict: { ... } doUpdate: { ... }

Batch Operations

Batch Insert

try database.write { db in
    try Item.insert {
        ($0.title, $0.isInStock)
    } values: {
        items.map { ($0.title, $0.isInStock) }
    }
    .execute(db)
}

Transaction Safety

All mutations inside database.write {} are wrapped in a transaction:

try database.write { db in
    // These all succeed or all fail together
    try Item.insert { ... }.execute(db)
    try Item.find(id).update { ... }.execute(db)
    try OtherTable.find(otherId).delete().execute(db)
}

If any operation throws, the entire transaction rolls back.


Raw SQL with #sql Macro

When you need custom SQL expressions beyond the type-safe query builder, use the #sql macro from StructuredQueries:

Custom Query Expressions

nonisolated extension Item.TableColumns {
    var isPastDue: some QueryExpression<Bool> {
        @Dependency(\.date.now) var now
        return !isCompleted && #sql("coalesce(date(\(dueDate)) < date(\(now)), 0)")
    }
}

// Use in queries
let overdue = try Item.where { $0.isPastDue }.fetchAll(db)

Raw SQL Queries

// Direct SQL with parameter interpolation
try #sql("SELECT * FROM items WHERE price > \(minPrice)").execute(db)

// Using \(raw:) for literal values
let tableName = "items"
try #sql("SELECT * FROM \(raw: tableName)").execute(db)
Why #sql
  • Type-safe parameter binding (prevents SQL injection)
  • Compile-time syntax checking
  • Seamless integration with query builder
  • Parameter interpolation automatically escapes values

For schema creation (CREATE TABLE, migrations), see the axiom-sqlitedata-ref reference skill for complete examples.


CloudKit Sync

Basic Setup

import CloudKit

extension DependencyValues {
    var defaultSyncEngine: SyncEngine {
        get { self[DefaultSyncEngineKey.self] }
        set { self[DefaultSyncEngineKey.self] = newValue }
    }
}

private enum DefaultSyncEngineKey: DependencyKey {
    static let liveValue = {
        @Dependency(\.defaultDatabase) var database
        return try! SyncEngine(
            for: database,
            tables: Item.self,
            privateTables: SensitiveItem.self,  // Private database
            startImmediately: true
        )
    }()
}

// In app init
prepareDependencies {
    $0.defaultDatabase = try! appDatabase()
    $0.defaultSyncEngine = try! SyncEngine(
        for: $0.defaultDatabase,
        tables: Item.self
    )
}

Manual Sync Control (v1.3.0+)

Control when sync happens instead of automatic background sync:

@Dependency(\.defaultSyncEngine) var syncEngine

// Pull changes from CloudKit
try await syncEngine.fetchChanges()

// Push local changes to CloudKit
try await syncEngine.sendChanges()

// Bidirectional sync
try await syncEngine.syncChanges()

Use cases:

  • User-triggered "Refresh" button
  • Sync after critical operations
  • Custom sync scheduling
  • Testing sync behavior

Sync State Observation (v1.2.0+)

Show UI feedback during sync:

struct SyncStatusView: View {
    @Dependency(\.defaultSyncEngine) var syncEngine

    var body: some View {
        HStack {
            if syncEngine.isSynchronizing {
                ProgressView()
                if syncEngine.isSendingChanges {
                    Text("Uploading...")
                } else if syncEngine.isFetchingChanges {
                    Text("Downloading...")
                }
            } else {
                Image(systemName: "checkmark.circle")
                Text("Synced")
            }
        }
    }
}

Observable properties:

  • isSendingChanges: Bool — True during CloudKit upload
  • isFetchingChanges: Bool — True during CloudKit download
  • isSynchronizing: Bool — True if either sending or fetching
  • isRunning: Bool — True if sync engine is active

Query Sync Metadata (v1.3.0+)

Access CloudKit sync information for records:

import CloudKit

// Get sync metadata for a record
let metadata = try SyncMetadata.find(item.syncMetadataID).fetchOne(db)

// Join items with their sync metadata
let itemsWithSync = try Item.all
    .leftJoin(SyncMetadata.all) { $0.syncMetadataID.eq($1.id) }
    .select { (item: $0, metadata: $1) }
    .fetchAll(db)

// Check if record is shared
let sharedItems = try Item.all
    .join(SyncMetadata.all) { $0.syncMetadataID.eq($1.id) }
    .where { $1.isShared }
    .fetchAll(db)

Migration Helpers

Migrate primary keys when switching sync strategies:

try await syncEngine.migratePrimaryKeys(
    from: OldItem.self,
    to: NewItem.self
)

When to Drop to GRDB

SQLiteData is built on GRDB. Use raw GRDB when you need:

  • Complex joins across 4+ tables
  • Window functions (ROW_NUMBER, RANK, etc.)
  • Performance-critical paths where you've profiled and confirmed the query builder is the bottleneck

See axiom-grdb for raw SQL patterns, ValueObservation, and DatabaseMigrator usage.


tvOS

SQLiteData with CloudKit SyncEngine is the recommended tvOS data solution. tvOS has no persistent local storage — the system deletes Caches (including Application Support) under storage pressure. With SyncEngine, iCloud is your persistent store and the local database is just a cache that rebuilds automatically after deletion. See axiom-tvos for full tvOS storage constraints.


Resources

GitHub: pointfreeco/sqlite-data, pointfreeco/swift-structured-queries, groue/GRDB.swift

Skills: axiom-sqlitedata-ref, axiom-sqlitedata-migration, axiom-database-migration, axiom-grdb


Targets: iOS 17+, Swift 6 Framework: SQLiteData 1.4+ History: See git log for changes

平台分布

Claude Code

30.86%
按下载量换算521

OpenCode

22.09%
按下载量换算373

Codex

16.6%
按下载量换算280

Cursor

11.69%
按下载量换算197

windsurf

7.6%
按下载量换算128

Antigravity

3.44%
按下载量换算58

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills