Vein

Write once. Persist anywhere.

Vein provides a safe, declarative and elegant foundation to handle data persistance for your Swift apps on any major platform.

Get Started
import VeinCore

enum V0_0_1: VersionedSchema {
    static let version = ModelVersion(0, 0, 1)
    static let models: [any PersistentModel.Type] = [Post.self, Tag.self]

    @Model
    final class Post {
        var title: String

        @LazyField
        var content: String

        @Relationship(inverse: \Tag.posts)
        var tags: [Tag]

        init(title: String, content: String) { /* ... */ }
    }

    @Model
    final class Tag {
        var name: String

        @Relationship
        var posts: [Post]
        /* ... */
    }
}
Supported platforms:

Build your persistence layer in a familiar way, shared across all platforms.

No need to learn new tools from scratch or integrate with SQLite directly. Veins high level abstraction offers simplicity, control and safety. SwiftUI, SwiftCrossUI or CLI tool, your models are always the same.

Vein

The shared engine below all surface targets. Contains the majority of API surface and interesting docs.

Learn more

VeinSwiftUI

Support for SwiftUI: the @Model macro, automatic UI updates for models, @Query for use in views and VeinContainer.

Learn more

VeinSCUI

Support for SwiftCrossUI: the @Model macro, automatic UI updates for models, @Query for use in views and VeinContainer.

Learn more

VeinCore

UI-framwork agnostic surface for use in CLI tools and not explicitly supported UI Frameworks: the @Model macro.

Learn more

Simple, safe migrations

Vein utilizes explicit migrations to ensure data integrity. Helpers let you handle simple schema updates, complex changes are made through fetch-transform-delete. If a migration is incomplete, Vein automatically reverts it to prevent data corruption.

Get Started
static let v1toV2 = MigrationStage.complex(
    fromVersion: V1.self,
    toVersion: V2.self,
    willMigrate: { context in
        try V1.Tag.unchangedMigration(
            to: V2.Tag.self,
            on: context
        )

        try V1.Post.fieldsAddedMigration(
            to: V2.Post.self
            on: context
        )
    }, didMigrate: nil)
}
static let v1toV2 = MigrationStage.complex(
    fromVersion: V1.self,
    toVersion: V2.self,
    willMigrate: { context in
        try V1.Tag.unchangedMigration(
            to: V2.Tag.self,
            on: context
        )

        try V1.Post.fieldsAddedMigration(
            to: V2.Post.self
            on: context
        )
    }, didMigrate: nil)
}

Simple, safe migrations

Vein utilizes explicit migrations to ensure data integrity. Helpers let you handle simple schema updates, complex changes are made through fetch-transform-delete. If a migration is incomplete, Vein automatically reverts it to prevent data corruption.

Get Started
let descriptor = try FetchDescriptor(
    predicate: #Predicate { post in
        post.title.contains("Vein")
        || post.title.starts(with: "Swift")
    },
    sortBy: [SortRule(\.id)]
)

let results = try context.fetch(descriptor)
// ---- OR -----
let results = try context.fetchAll(
    #Predicate { post in
        post.title.contains("Vein")
        || post.title.starts(with: "Swift")
    }
)

Swifty Queries

Vein supports query building using idiomatic Swift. Use #Predicate or #Filter, writing a closure like you would for filter(_:) or create a ModelPredicate if you want direct control over the query.

Get Started
let descriptor = try FetchDescriptor(
    predicate: #Predicate { post in
        post.title.contains("Vein")
        || post.title.starts(with: "Swift")
    },
    sortBy: [SortRule(\.id)]
)

let results = try context.fetch(descriptor)
// ---- OR -----
let results = try context.fetchAll(
    #Predicate { post in
        post.title.contains("Vein")
        || post.title.starts(with: "Swift")
    }
)

Swifty Queries

Vein supports query building using idiomatic Swift. Use #Predicate or #Filter, writing a closure like you would for filter(_:) or create a ModelPredicate if you want direct control over the query.

Get Started

Direct UI integration

Vein integrates directly with SwiftUI and SwiftCrossUI Views via the declarative, auto-updating @Query property wrapper and ObservableObject models.

Get Started
import VeinSwiftUI

struct PostList: View {
    @Query(sortBy: [SortRule(\.id, order: .descending)])
    var posts: [Post]

    var body: some View {
        List(posts) { post in
            Text(post.title)
        }
    )
}
import VeinSwiftUI

struct PostList: View {
    @Query(sortBy: [SortRule(\.id, order: .descending)])
    var posts: [Post]

    var body: some View {
        List(posts) { post in
            Text(post.title)
        }
    )
}

Direct UI integration

Vein integrates directly with SwiftUI and SwiftCrossUI Views via the declarative, auto-updating @Query property wrapper and ObservableObject models.

Get Started
import Testing
import VeinCore
import VeinTesting

@Test
func testMigration() async throws {
    let tester = try MigrationTester(
        migrationPlan: MigrationPlan.self
    )
    try tester.testCompleteChain(
        initialData: { context in
            // Seed the context
        },
        validations: [
            V2.version: { context in
                // Validate V1 to V2 here
            }
        ]
    )
}

Migration Test Support

Migrations going wrong are a common case for data loss. To help you prevent that, Vein comes with VeinTesting, a thin layer on top of Vein to make testing migrations more pleasant.

Get Started
import Testing
import VeinCore
import VeinTesting

@Test
func testMigration() async throws {
    let tester = try MigrationTester(
        migrationPlan: MigrationPlan.self
    )
    try tester.testCompleteChain(
        initialData: { context in
            // Seed the context
        },
        validations: [
            V2.version: { context in
                // Validate V1 to V2 here
            }
        ]
    )
}

Migration Test Support

Migrations going wrong are a common case for data loss. To help you prevent that, Vein comes with VeinTesting, a thin layer on top of Vein to make testing migrations more pleasant.

Get Started