Skip to content

brightdigit/DataThespian

Repository files navigation

DataThespian

SwiftPM Twitter GitHub GitHub issues GitHub Workflow Status

Codecov CodeFactor Grade codebeat badge Code Climate maintainability Code Climate technical debt Code Climate issues

Table of Contents

Introduction

DataThespian is a thread-safe SwiftData implementation that uses the power of ModelActors to provide an optimized and type-safe database interface. It offers a clean API for common database operations while maintaining concurrency safety and preventing common SwiftData pitfalls.

Key features:

  • Thread-safe database operations using ModelActor
  • Type-safe query interface with Selectors
  • SwiftUI integration via Environment
  • Support for monitoring database changes
  • Collection synchronization utilities

Requirements

Apple Platforms

  • Xcode 16.0 or later
  • Swift 6.0 or later
  • iOS 17 / watchOS 10.0 / tvOS 17 / macOS 14 or later deployment targets

Linux

  • Ubuntu 20.04 or later
  • Swift 6.0 or later

Installation

To integrate DataThespian into your app using SPM, specify it in your Package.swift file:

let package = Package(
  ...
  dependencies: [
    .package(url: "https://github.com/brightdigit/DataThespian.git", from: "1.0.0")
  ],
  targets: [
      .target(
          name: "YourApps",
          dependencies: [
            .product(name: "DataThespian", package: "DataThespian"), ...
          ]),
      ...
  ]
)

Usage

Setting up Database

When working with SwiftData, it's crucial to use a single ModelContext throughout your app. There are two ways to create your database:

Using Built-in ModelActorDatabase

// Create a database using the built-in ModelActorDatabase
let database = ModelActorDatabase(modelContainer: container)

Creating Your Own Database Type

You can also create your own database type by implementing the Database protocol:

@ModelActor
actor CustomDatabase: Database {
}

Using SharedDatabase

To avoid issues with multiple ModelContexts being created each time SwiftUI redraws views, use SharedDatabase to ensure a single shared context:

public struct SharedDatabase {
    public static let shared: SharedDatabase = .init()

    public let schemas: [any PersistentModel.Type]
    public let modelContainer: ModelContainer
    public let database: any Database

    private init(
        schemas: [any PersistentModel.Type] = .all,
        modelContainer: ModelContainer? = nil,
        database: (any Database)? = nil
    ) {
        self.schemas = schemas
        let modelContainer = modelContainer ?? .forTypes(schemas)
        self.modelContainer = modelContainer
        self.database = database ?? ModelActorDatabase(modelContainer: modelContainer)
    }
}

Then set up the database in your SwiftUI app:

var body: some Scene {
    WindowGroup {
        RootView()
    }
    .database(SharedDatabase.shared.database)
    /* If you need @Query support
    .modelContainer(SharedDatabase.shared.modelContainer)
    */
}

Access the database in your views using the environment:

@Environment(\.database) private var database

Making Queries

DataThespian provides a type-safe way to query your data:

// Fetch a single item
let item = try await database.get(for: .predicate(#Predicate<Item> { 
    $0.name == "Test" 
}))

// Fetch multiple items with sorting
let items = await database.fetch(for: .descriptor(
    predicate: #Predicate<Item> { $0.isActive },
    sortBy: [SortDescriptor(\Item.timestamp, order: .reverse)]
))

// Insert new item
let timestamp = Date()
let newItem = await database.insert { 
    Item(name: "Test", timestamp: timestamp) 
}

// Save changes
try await database.save()

// Re-query after save using a unique field
let savedItem = try await database.getOptional(for: .predicate(#Predicate<Item> { 
    $0.timestamp == timestamp 
}))

Documentation

To learn more, check out the full documentation.

License

This code is distributed under the MIT license. See the LICENSE file for more info.