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.
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 Startedstatic 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 Startedlet 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 Startedlet 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 StartedDirect UI integration
Vein integrates directly with SwiftUI and SwiftCrossUI Views via the declarative, auto-updating @Query property wrapper and ObservableObject models.
Get Startedimport 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 Startedimport 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 Startedimport 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