About 1 month after its initial release, I'm happy to announce Amethyst Vein version 1.1
Vein 1.1 brings new features, some bug fixes and wider platform compatibility (and even more tests).
Let's start with compatibility:
Vein 1.0 supported macOS 14+ and iOS 17+. There was a request to support macOS 13 and iOS 16. Vein 1.1 delivers.
The issue was #Predicate, which is the primary way to filter models in Vein. It didn't support older versions.
I decided to take the open source implementation from Swift Foundation and adapt it to function.
The adapted version lives in the amethystsoft/vein-filter repository. It does not depend on Vein. I had to find alternatives to some features it used, for example the repeat and the each keyword. The ported predicate supports up to 3 input types, Vein continues to support one, I just thought that way it might be more useful for the community.
To use it just enable the VeinFilter trait and use #Filter where and how you would use #Predicate.
Bug fixes:
There's only one big fix: #119 Models both inserted and updated without saving return twice on fetch
Like the title says, when you inserted a model, then updated and fetched it without saving it would appear in the results twice. When fetching from the database, the context processes deleted, inserted, and then updated models sequentially.
The loop iterating over all inserted ones adding matching models to the results didn't update the ids that were added. So the updated loop didn't know it was already added and added it again.
A smaller fix affected multithreaded stability when using SwiftCrossUI. A SwiftCrossUI Publisher isn't synchronized, so I added synchronization myself to avoid potential datarace caught by thread sanitizer. I don't think this really was an issue in reality, but it's still fixed.
Features
- added
SortRulefor sorting queries. Supports case-insensitive(default) and lexical String sorting. - added
FetchDescriptorandPaginatedFetchDescriptor, giving you more control over what's fetched and what the context respects for filtering.- supports ignoring in memory changes (included by default for
FetchDescriptor, always off forPaginatedFetchDescriptor). Pending deletions are always respected, because they aren't managed by the context anymore. PaginatedFetchDescriptorsupports limit & offset.- supports
Predicate,Filter,ModelPredicateandany PersistentModel.Type. - supports
SortRule. - usable via new
ManagedObjectContext/fetch(_:)
- supports ignoring in memory changes (included by default for
- added support for
SortRuleandFilterto the@Queryproperty wrappers - added
ManagedObjectContext/fetchCount(_:)acceptingFetchDescriptor. Pending changes are always ignored. ModelContainer/getConnection()is now public- added
ModelConfigurationcurrently giving you control over clearing stale identitymap entries after save (default true) or a timeout after which they're cleaned automatically (default off). @Modelmacro now fails to compile when attempting to write to a relationship inside the initializer. That would crash at runtime and be hard to debug before. I achieved this by parsing the AST of initializer bodies. Setting to properties with the same name not resolving to self is still allowed, excluding inout parameters (which you shouldn't use inside an initializer anyway in my opinion). To benefit from this compiletime check, please make sure to only add initializers inside the declaration of the model.- added
PersistentModel/isManagedcomputed property, which is just more convenient. - removed notification debouncing for inserts, which should make inserts feel faster in UI applications.
- fatalError messages were improved in some places to provide the most useful information possible.
- some improvements to documentation
Check out the new "Advanced Queries" tutorial showing you how to use some of the new features.
Example:
func fetchPostsMentioningSwiftSortedByTitle(
page: Int,
on context: ManagedObjectContext
) -> [Post] {
let fetchDescriptor = PaginatedFetchDescriptor(
filter: #Filter<Post> { post in
post.content.contains("Swift")
},
sortBy: [SortRule(\.title)],
limit: 20,
offset: page * 20
)
return try context.fetch(fetchDescriptor)
}
Other improvements
The test suite was expanded covering the new features. Also even more thorough multithreaded tests were added, providing some more chances for thread sanitizer to catch things.
There is also a new homepage for vein now: vein.amethystsoft.de.
Mentions
Thanks to Xu Yang for his consultation on what Vein should do regarding pagination (including in memory changes or not). Also thanks to stackotter, Beberka and Skye for occasionally helping with API design feedback.
Roadmap
Vein 1.1 provides an even stronger foundation than 1.0 adding some features crucial for a database framework that were missing.
A benchmark by Xu Yang showed Vein is already performing great. But it also showed some things where it can improve even further:
- optionally eager fetching certain lazy properties
- there appeared to be some baseline overhead which made SwiftData slightly outperform Vein in timing benchmarks in some specific cases. I want to investigate that and potentially improve. [Read more]
I also want to expand the amount of SQLite errors I can convert to a specific enum case and bring even more validations to the VeinTesting framework.
With Vein now supporting the most important things, I will start working on the planned open-source, self-hostable sync solution, so you can not only have the same models everywhere but also the same data.
Once Swift 6.4 is released I'm going to try making Vein work with Skip.
If you would like to request a feature, please open a thread in discussions like described in section 2 of CONTRIBUTING.md
Projects using Vein
There are 3 examples apps (SwiftUI, SwiftCrossUI and CLI) to help you get started. The CLI example might actually be useful to you if you often need regex replace.
I'm also going to start migrating the browser history of Amethyst Browser to Vein. Once I have a sync solution, there will be a Vein powered password manager, integrated directly into it. I built Vein because I needed it - and soon I'm going to use it in my every-day tools.
Supporting the Project
As mentioned in the 1.0 announcement, Vein remains an independent project, and support from the community is what keeps it going.
If you're finding Vein useful, the best ways to help right now are starring the GitHub repository, sharing the project, or exploring GitHub Sponsorships & Commercial Licensing/Support if your team uses it in production.
Thank you for reading, Mia