├── .gitignore ├── Sources ├── SwiftDB │ ├── module.swift │ ├── Intermodular │ │ ├── Helpers │ │ │ ├── CloudKit │ │ │ │ └── _CloudKit.swift │ │ │ └── CoreData │ │ │ │ ├── _CoreData.swift │ │ │ │ ├── NSPersistentStoreType.swift │ │ │ │ ├── _CoreData.PersistentStoreDescription.swift │ │ │ │ ├── _CoreData._SwiftDB_NSManagedObject.swift │ │ │ │ ├── _CoreData._SwiftDB_NSEntityDescription.swift │ │ │ │ ├── NSManagedObjectModel+DatabaseSchema.swift │ │ │ │ └── NSManagedObjectContext.ChangesPublisher.swift │ │ ├── Protocol Conformances │ │ │ └── CoreData │ │ │ │ ├── CoreData+Codable.swift │ │ │ │ ├── CoreData+DatabaseSchemaAdaptor.swift │ │ │ │ └── CoreData+DatabaseZone.swift │ │ └── Extensions │ │ │ ├── CoreData │ │ │ ├── NSEntityDescription++.swift │ │ │ ├── NSPersistentStore++.swift │ │ │ ├── NSManagedObject++.swift │ │ │ ├── NSManagedObjectContext++.swift │ │ │ ├── NSPersistentStoreCoordinator++.swift │ │ │ └── NSPersistentContainer++.swift │ │ │ └── CloudKit │ │ │ ├── CKContainer++.swift │ │ │ ├── CKRecord.ID++.swift │ │ │ └── CKError++.swift │ └── Intramodular │ │ ├── Miscellaneous │ │ ├── _SwiftDB_Error.swift │ │ ├── Metadata │ │ │ ├── DatabaseMetadata.swift │ │ │ └── DatabaseCapability.swift │ │ ├── DatabaseStateFileFormat.swift │ │ ├── Conflict Resolution │ │ │ └── DatabaseRecordMergeConflict.swift │ │ ├── RawRepresentable+Extensions.swift │ │ ├── _GenericKeyValueCoder.swift │ │ ├── Record Space │ │ │ ├── DatabaseRecordSpace.SaveError.swift │ │ │ └── DatabaseRecordSpace.swift │ │ └── Dump │ │ │ ├── _DatabaseRecordDump.swift │ │ │ └── _DatabaseDump.swift │ │ ├── Type-erasers │ │ ├── AnyDatabase+Helpers.swift │ │ ├── AnyDatabaseQuerySubscription.swift │ │ ├── AnyDatabaseSchemaAdaptor.swift │ │ ├── AnyDatabaseZone.swift │ │ ├── AnyDatabaseTransactionExecutor.swift │ │ ├── AnyDatabaseRecord.RecordType.swift │ │ ├── AnyDatabaseTransaction.swift │ │ ├── AnyDatabaseRecord.swift │ │ ├── AnyDatabaseRecordSpace.swift │ │ └── AnyDatabase.swift │ │ ├── Migration │ │ ├── AttributeMigrationPolicy.swift │ │ ├── DatabaseMigrationCheck.swift │ │ ├── UnsafeRecordMigrationDestination.swift │ │ └── Schema Mapping │ │ │ └── _EntitySchemaMigrationMapping.swift │ │ ├── Foundation │ │ ├── LocalDatabase.swift │ │ ├── DatabaseZone.swift │ │ ├── Entity.swift │ │ ├── DatabaseContext.swift │ │ ├── Database.swift │ │ └── _opaque_Entity.swift │ │ ├── Property Wrappers │ │ ├── EntityAttributeTrait.swift │ │ ├── EntityPropertyAccessor.swift │ │ ├── ObservedModel.swift │ │ ├── EntityPropertyAccessorModifier.swift │ │ └── QueryModels.swift │ │ ├── Schema │ │ ├── _SchemaRepresentable.swift │ │ ├── _SchemaHistory.swift │ │ ├── Schema.swift │ │ ├── DatabaseSchemaAdaptor.swift │ │ ├── SchemaBuilder.swift │ │ ├── PrimitiveAttributeDataType.swift │ │ ├── _Schema.Record.swift │ │ ├── Entity │ │ │ ├── _Schema.Entity.Attribute.swift │ │ │ └── _Schema.Entity.Property.swift │ │ └── _EntityAttributeSchemaRepresentable.swift │ │ ├── Query │ │ ├── DatabaseQuerySubscription.swift │ │ ├── QuerySubscription.swift │ │ └── QueryRequest.swift │ │ ├── Container │ │ ├── AnyDatabaseContainer+Codable.swift │ │ ├── LocalDatabaseContainer+Initializers.swift │ │ ├── AnyDatabaseContainer.swift │ │ └── _AssociateDatabaseViewModifier.swift │ │ ├── Transactions │ │ ├── DatabaseTransaction.swift │ │ ├── DatabaseTransactionExecutor.swift │ │ ├── AnyTransaction.swift │ │ └── AnyLocalTransaction.swift │ │ ├── Runtime │ │ ├── _AnyDatabaseTaskRuntime.swift │ │ ├── _SwiftDB_TaskLocalValues.swift │ │ ├── _SwiftDB_TaskRuntime.swift │ │ └── _SwiftDB_Runtime.swift │ │ ├── Records │ │ ├── DatabaseRecordConfiguration.swift │ │ ├── RecordSnapshot.swift │ │ ├── DatabaseRecord.swift │ │ ├── Coding │ │ │ ├── _RecordFieldPayload.swift │ │ │ ├── _RecordFieldPayloadConvertible.swift │ │ │ └── _DatabaseRecordDataDecoder.swift │ │ ├── DatabaseRecordUpdate.swift │ │ └── Proxy │ │ │ └── _DatabaseRecordSnapshot.swift │ │ ├── Relationships │ │ ├── _EntityRelationshipDestination.swift │ │ └── RelatedModels.swift │ │ └── CRUDQ │ │ ├── LocalDatabaseCRUDQ.swift │ │ └── DatabaseCRUDQ.swift ├── _CoreDataPrivate │ └── _CoreDataPrivate.xcframework │ │ ├── macos-arm64e-arm64-x86_64 │ │ └── _CoreDataPrivate.framework │ │ │ ├── Versions │ │ │ ├── Current │ │ │ └── A │ │ │ │ ├── Headers │ │ │ │ ├── _CoreDataPrivate.h │ │ │ │ └── NSManagedObjectID+Private.h │ │ │ │ ├── Modules │ │ │ │ ├── module.modulemap │ │ │ │ └── _CoreDataPrivate.swiftmodule │ │ │ │ │ └── arm64e-apple-macos.swiftinterface │ │ │ │ ├── _CoreDataPrivate │ │ │ │ ├── Resources │ │ │ │ └── Info.plist │ │ │ │ └── _CoreDataPrivate.tbd │ │ │ ├── Resources │ │ │ ├── _CoreDataPrivate │ │ │ ├── Headers │ │ │ ├── _CoreDataPrivate.h │ │ │ └── NSManagedObjectID+Private.h │ │ │ ├── Modules │ │ │ ├── module.modulemap │ │ │ └── _CoreDataPrivate.swiftmodule │ │ │ │ └── arm64e-apple-macos.swiftinterface │ │ │ ├── Info.plist │ │ │ └── _CoreDataPrivate.tbd │ │ └── Info.plist ├── _SwiftDataPrivate │ └── _SwiftDataPrivate.xcframework │ │ ├── macos-arm64e-arm64-x86_64 │ │ └── _SwiftDataPrivate.framework │ │ │ ├── Versions │ │ │ ├── Current │ │ │ └── A │ │ │ │ ├── Headers │ │ │ │ └── _SwiftDataPrivate.h │ │ │ │ ├── Modules │ │ │ │ ├── module.modulemap │ │ │ │ └── _SwiftDataPrivate.swiftmodule │ │ │ │ │ └── arm64e-apple-macos.swiftinterface │ │ │ │ ├── _SwiftDataPrivate │ │ │ │ ├── Resources │ │ │ │ └── Info.plist │ │ │ │ └── _SwiftDataPrivate.tbd │ │ │ ├── Headers │ │ │ └── _SwiftDataPrivate.h │ │ │ ├── Resources │ │ │ ├── _SwiftDataPrivate │ │ │ ├── Modules │ │ │ ├── module.modulemap │ │ │ └── _SwiftDataPrivate.swiftmodule │ │ │ │ └── arm64e-apple-macos.swiftinterface │ │ │ ├── Info.plist │ │ │ └── _SwiftDataPrivate.tbd │ │ └── Info.plist ├── _SwiftDataToolboxCShims │ ├── include │ │ └── NSEntityDescription+Category.h │ └── NSEntityDescription+Category.m └── SwiftDataToolbox │ └── AdoptCoreSpotlight.swift ├── Samples └── SwiftDBTest │ ├── SwiftDBTest │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── SwiftDBTest.entitlements │ ├── App.swift │ ├── Entity.swift │ └── ContentView.swift │ └── SwiftDBTest.xcodeproj │ └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── swiftpm │ └── Package.resolved ├── .editorconfig ├── Tests ├── XCTestManifests.swift ├── _SwiftDB_RuntimeTests.swift ├── TestORMSchema.swift └── NSPersistentContainerTests.swift ├── .vscode └── settings.json ├── LICENSE.md ├── Package.swift ├── README.md └── Package.resolved /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .swiftpm/ 3 | .swiftpm/* 4 | /*.xcodeproj 5 | /.build 6 | /Packages 7 | xcuserdata/ 8 | -------------------------------------------------------------------------------- /Sources/SwiftDB/module.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | @_exported import Diagnostics 6 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/Current: -------------------------------------------------------------------------------- 1 | A -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/A/Resources -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Headers/_SwiftDataPrivate.h: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Resources: -------------------------------------------------------------------------------- 1 | Versions/A/Resources -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/_CoreDataPrivate: -------------------------------------------------------------------------------- 1 | Versions/A/_CoreDataPrivate -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/A/Headers/_SwiftDataPrivate.h: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CloudKit/_CloudKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | public enum _CloudKit { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/_SwiftDataPrivate: -------------------------------------------------------------------------------- 1 | Versions/A/_SwiftDataPrivate -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Headers/_CoreDataPrivate.h: -------------------------------------------------------------------------------- 1 | #import "NSManagedObjectID+Private.h" -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/Headers/_CoreDataPrivate.h: -------------------------------------------------------------------------------- 1 | #import "NSManagedObjectID+Private.h" -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.swift] 2 | indent_style = space 3 | indent_size = 4 4 | tab_width = 4 5 | end_of_line = lf 6 | insert_final_newline = true 7 | max_line_length = 100 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CoreData/_CoreData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swift 7 | 8 | public enum _CoreData { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/_SwiftDB_Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public protocol _SwiftDB_Error: Error { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabase+Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | protocol _AnyDatabaseRuntimeCasting { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Migration/AttributeMigrationPolicy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public enum AttributeMigrationPolicy { 8 | case discardCurrent 9 | } 10 | -------------------------------------------------------------------------------- /Sources/_SwiftDataToolboxCShims/include/NSEntityDescription+Category.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface NSEntityDescription (Category) 6 | 7 | @end 8 | 9 | NS_ASSUME_NONNULL_END 10 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/Metadata/DatabaseMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct DatabaseMetadata { 8 | public var capabilities: [DatabaseCapability] 9 | } 10 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Protocol Conformances/CoreData/CoreData+Codable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Merge 7 | import Swallow 8 | 9 | extension NSDeleteRule: Codable { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/DatabaseStateFileFormat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct DatabaseStateFileFormat { 8 | public static let pathExtension = "dbstate" 9 | } 10 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module _CoreDataPrivate { 2 | umbrella header "_CoreDataPrivate.h" 3 | export * 4 | module * { export * } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module _SwiftDataPrivate { 2 | umbrella header "_SwiftDataPrivate.h" 3 | export * 4 | module * { export * } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module _CoreDataPrivate { 2 | umbrella header "_CoreDataPrivate.h" 3 | export * 4 | module * { export * } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/A/Modules/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module _SwiftDataPrivate { 2 | umbrella header "_SwiftDataPrivate.h" 3 | export * 4 | module * { export * } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Foundation/LocalDatabase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CorePersistence 6 | import Merge 7 | import Swallow 8 | 9 | public protocol LocalDatabase: Database, FolderEnclosable where Zone: FolderEnclosable { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Tests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import XCTest 6 | 7 | #if !canImport(ObjectiveC) 8 | 9 | public func allTests() -> [XCTestCaseEntry] { 10 | return [ 11 | testCase(SwiftDBTests.allTests), 12 | ] 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /Sources/_SwiftDataToolboxCShims/NSEntityDescription+Category.m: -------------------------------------------------------------------------------- 1 | #import "NSEntityDescription+Category.h" 2 | @import SwiftDataToolbox; 3 | 4 | @implementation NSEntityDescription (Category) 5 | 6 | + (void)load { 7 | __vmanot_swiftDB_doNotCall_NSEntityDescription_load(); 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/_CoreDataPrivate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmanot/SwiftDB/HEAD/Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/_CoreDataPrivate -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Property Wrappers/EntityAttributeTrait.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swift 6 | 7 | public enum EntityAttributeTrait: String, Codable { 8 | case guaranteedUnique 9 | case typeDiscriminator 10 | case allowsExternalBinaryDataStorage 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/_SchemaRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Swift 7 | 8 | public protocol _SchemaRepresentable { 9 | typealias Context = Void 10 | 11 | func makeSchema(context: Context) throws -> _Schema 12 | } 13 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Headers/NSManagedObjectID+Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface NSManagedObjectID (Private) 6 | - (NSString *)storeIdentifier; 7 | @end 8 | 9 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/A/_SwiftDataPrivate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmanot/SwiftDB/HEAD/Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/A/_SwiftDataPrivate -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/_SchemaHistory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct _SchemaHistory: Codable, Hashable, Sendable { 8 | @LossyCoding 9 | public var schemas: [_Schema] = [] 10 | 11 | public init() { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/Headers/NSManagedObjectID+Private.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface NSManagedObjectID (Private) 6 | - (NSString *)storeIdentifier; 7 | @end 8 | 9 | NS_ASSUME_NONNULL_END -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "swift.path": "/usr/bin/", 3 | "swift.swiftEnvironmentVariables": { 4 | "DEVELOPER_DIR": "/Applications/Xcode-16.3.0.app" 5 | }, 6 | "lldb.library": "/Applications/Xcode-16.3.0.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB", 7 | "lldb.launch.expressions": "native", 8 | "swiftlint.enable": false 9 | } -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Foundation/DatabaseZone.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | /// A database zone. 9 | /// 10 | /// This is a flexible concept that spans across RDBMSs to document-oriented databases. 11 | public protocol DatabaseZone: Identifiable where ID: Codable & Sendable { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/Conflict Resolution/DatabaseRecordMergeConflict.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swift 6 | 7 | /// An encapsulation of conflicts that occur during an attempt to save changes in a database record space. 8 | public struct DatabaseRecordMergeConflict { 9 | let source: Context.Record 10 | } 11 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest/SwiftDBTest.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Migration/DatabaseMigrationCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swallow 7 | 8 | /// A type that encapsulates the assessment of a database to check whether any of its zones require a migration. 9 | public struct DatabaseMigrationCheck: Sendable { 10 | public let zonesToMigrate: Set 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/RawRepresentable+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | extension RawRepresentable { 8 | static var _opaque_RawValue: Any.Type { 9 | RawValue.self 10 | } 11 | 12 | init?(_opaque_rawValue rawValue: Any) throws { 13 | self.init(rawValue: try cast(rawValue, to: RawValue.self)) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Query/DatabaseQuerySubscription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Combine 6 | import Swallow 7 | 8 | /// A subscription to a database query. 9 | /// 10 | /// A query subscription is also a `Publisher` that can be subscribed to. 11 | public protocol DatabaseQuerySubscription: Publisher where Output == [Database.Record], Error == Swift.Error { 12 | associatedtype Database: SwiftDB.Database 13 | } 14 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/_GenericKeyValueCoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | protocol _GenericKeyValueCoder { 8 | func decode(_ type: Value.Type, forKey key: Key) throws -> Value 9 | func encode(_ value: Value, forKey key: Key) throws 10 | func remove(_ type: Value.Type, forKey key: Key) throws -> Value 11 | } 12 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CoreData/NSEntityDescription++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swallow 7 | 8 | extension NSEntityDescription { 9 | var allKeys: [CodingKey] { 10 | properties.map({ AnyStringKey(stringValue: $0.name) }) + (superentity?.allKeys ?? []) 11 | } 12 | 13 | func contains(key: Key) -> Bool { 14 | allKeys.contains(where: { $0.stringValue == key.stringValue }) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleInfoDictionaryVersion 6 | 6.0 7 | CFBundlePackageType 8 | FMWK 9 | CFBundleShortVersionString 10 | 1.0.0 11 | 12 | 13 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleInfoDictionaryVersion 6 | 6.0 7 | CFBundlePackageType 8 | FMWK 9 | CFBundleShortVersionString 10 | 1.0.0 11 | 12 | 13 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/Schema.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | /// The schema of a data model. 8 | public protocol Schema { 9 | // FIXME: This needs a `SchemaRepresentation` protocol to be created, right now it's a dumb type. 10 | typealias Body = [any Entity.Type] 11 | 12 | /// The body of this schema. 13 | /// Use this to compose and declare the entities & models encapsulated by this schema. 14 | @SchemaBuilder 15 | var body: Body { get } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleInfoDictionaryVersion 6 | 6.0 7 | CFBundlePackageType 8 | FMWK 9 | CFBundleShortVersionString 10 | 1.0.0 11 | 12 | 13 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/A/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleInfoDictionaryVersion 6 | 6.0 7 | CFBundlePackageType 8 | FMWK 9 | CFBundleShortVersionString 10 | 1.0.0 11 | 12 | 13 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/DatabaseSchemaAdaptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public protocol DatabaseSchemaAdaptor { 8 | associatedtype Database: SwiftDB.Database 9 | 10 | /// The corresponding record type for a given entity. 11 | func recordType(for entity: _Schema.Entity.ID?) throws -> Database.Record.RecordType 12 | 13 | /// The corresponding entity ID for a given record type. 14 | func entity(forRecordType recordType: Database.Record.RecordType) throws -> _Schema.Entity.ID? 15 | } 16 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest/App.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ------------------------------------------------ 3 | // Original project: SwiftDBTest 4 | // Created on 2025/5/14 by Fatbobman(东坡肘子) 5 | // X: @fatbobman 6 | // Mastodon: @fatbobman@mastodon.social 7 | // GitHub: @fatbobman 8 | // Blog: https://fatbobman.com 9 | // ------------------------------------------------ 10 | // Copyright © 2025-present Fatbobman. All rights reserved. 11 | 12 | 13 | import SwiftUI 14 | 15 | @main 16 | struct SwiftDBTestApp: App { 17 | var body: some Scene { 18 | WindowGroup { 19 | ContentView() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CloudKit/CKContainer++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CloudKit 6 | import Swallow 7 | 8 | extension CKContainer { 9 | func database(for scope: CKDatabase.Scope) throws -> CKDatabase { 10 | switch scope { 11 | case .`public`: 12 | return publicCloudDatabase 13 | case .`private`: 14 | return privateCloudDatabase 15 | case .shared: 16 | return sharedCloudDatabase 17 | @unknown default: 18 | throw Never.Reason.unimplemented 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CoreData/NSPersistentStoreType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swift 7 | 8 | /// A persistent store type. 9 | public enum NSPersistentStoreType { 10 | case binaryStore 11 | case inMemory 12 | case sqlite 13 | 14 | public var rawValue: String { 15 | switch self { 16 | case .binaryStore: 17 | return NSBinaryStoreType 18 | case .inMemory: 19 | return NSInMemoryStoreType 20 | case .sqlite: 21 | return NSSQLiteStoreType 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CloudKit/CKRecord.ID++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CloudKit 6 | import Swallow 7 | 8 | extension CKRecord.ID { 9 | public convenience init( 10 | recordName: String?, 11 | zoneID: CKRecordZone.ID? 12 | ) { 13 | if let zoneID = zoneID { 14 | self.init( 15 | recordName: recordName ?? UUID().uuidString, 16 | zoneID: zoneID 17 | ) 18 | } else { 19 | self.init( 20 | recordName: recordName ?? UUID().uuidString 21 | ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Container/AnyDatabaseContainer+Codable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | // MARK: - Auxiliary 9 | 10 | extension Dictionary where Key == CodingUserInfoKey, Value == Any { 11 | var _SwiftDB_DatabaseContainer: AnyDatabaseContainer! { 12 | get { 13 | self[._SwiftDB_DatabaseContainer] as? AnyDatabaseContainer 14 | } set { 15 | self[._SwiftDB_DatabaseContainer] = newValue 16 | } 17 | } 18 | } 19 | 20 | extension CodingUserInfoKey { 21 | fileprivate static let _SwiftDB_DatabaseContainer = CodingUserInfoKey(rawValue: "_SwiftDB_DatabaseContainer")! 22 | } 23 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/SchemaBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | import Swift 7 | 8 | @resultBuilder 9 | public final class SchemaBuilder { 10 | public typealias Element = any Entity.Type 11 | 12 | public static func buildBlock() -> [Element] { 13 | [] 14 | } 15 | 16 | public static func buildBlock(_ element: Element) -> [Element] { 17 | [element] 18 | } 19 | 20 | public static func buildBlock(_ elements: Element...) -> [Element] { 21 | elements 22 | } 23 | 24 | public static func buildBlock(_ elements: [Element]) -> [Element] { 25 | elements 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/_CoreDataPrivate.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd 2 | tbd-version: 4 3 | targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 4 | arm64e-macos, arm64e-maccatalyst ] 5 | install-name: '/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData' 6 | swift-abi-version: 7 7 | exports: 8 | - targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 9 | arm64e-macos, arm64e-maccatalyst ] 10 | symbols: [ ] 11 | objc-classes: [ ] 12 | objc-ivars: [ ] 13 | ... 14 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/_SwiftDataPrivate.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd 2 | tbd-version: 4 3 | targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 4 | arm64e-macos, arm64e-maccatalyst ] 5 | install-name: '/System/Library/Frameworks/SwiftData.framework/Versions/A/SwiftData' 6 | swift-abi-version: 7 7 | exports: 8 | - targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 9 | arm64e-macos, arm64e-maccatalyst ] 10 | symbols: [ ] 11 | objc-classes: [ ] 12 | objc-ivars: [ ] 13 | ... 14 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/Metadata/DatabaseCapability.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | /// A (user-definable) key for describing the set of supported database capabilities. 8 | public struct DatabaseCapability: Codable, Hashable, RawRepresentable { 9 | public let rawValue: String 10 | 11 | public init(rawValue: String) { 12 | self.rawValue = rawValue 13 | } 14 | } 15 | 16 | extension DatabaseCapability { 17 | public static let schemaless = Self(rawValue: "schemaless") 18 | public static let schemafull = Self(rawValue: "schemafull") 19 | public static let relationships = Self(rawValue: "relationships") 20 | } 21 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/_CoreDataPrivate.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd 2 | tbd-version: 4 3 | targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 4 | arm64e-macos, arm64e-maccatalyst ] 5 | install-name: '/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData' 6 | swift-abi-version: 7 7 | exports: 8 | - targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 9 | arm64e-macos, arm64e-maccatalyst ] 10 | symbols: [ ] 11 | objc-classes: [ ] 12 | objc-ivars: [ ] 13 | ... 14 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/A/_SwiftDataPrivate.tbd: -------------------------------------------------------------------------------- 1 | --- !tapi-tbd 2 | tbd-version: 4 3 | targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 4 | arm64e-macos, arm64e-maccatalyst ] 5 | install-name: '/System/Library/Frameworks/SwiftData.framework/Versions/A/SwiftData' 6 | swift-abi-version: 7 7 | exports: 8 | - targets: [ x86_64-macos, x86_64-maccatalyst, arm64-macos, arm64-maccatalyst, 9 | arm64e-macos, arm64e-maccatalyst ] 10 | symbols: [ ] 11 | objc-classes: [ ] 12 | objc-ivars: [ ] 13 | ... 14 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest/Entity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ------------------------------------------------ 3 | // Original project: SwiftDBTest 4 | // Created on 2025/5/14 by Fatbobman(东坡肘子) 5 | // X: @fatbobman 6 | // Mastodon: @fatbobman@mastodon.social 7 | // GitHub: @fatbobman 8 | // Blog: https://fatbobman.com 9 | // ------------------------------------------------ 10 | // Copyright © 2025-present Fatbobman. All rights reserved. 11 | 12 | import SwiftDB 13 | 14 | struct Foo: Entity, Identifiable { 15 | @Attribute var bar: String = "Untitled" 16 | 17 | var id: some Hashable { 18 | bar 19 | } 20 | } 21 | 22 | struct MySchema: Schema { 23 | var body: [any Entity.Type] { 24 | Foo.self 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/Record Space/DatabaseRecordSpace.SaveError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Foundation 6 | import Merge 7 | import Swallow 8 | 9 | public struct DatabaseRecordSpaceSaveError: CustomStringConvertible, LocalizedError { 10 | public let description: String 11 | public let mergeConflicts: [DatabaseRecordMergeConflict]? 12 | 13 | public var errorDescription: String? { 14 | description 15 | } 16 | 17 | public init( 18 | description: String, 19 | mergeConflicts: [DatabaseRecordMergeConflict]? 20 | ) { 21 | self.description = description 22 | self.mergeConflicts = mergeConflicts 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Transactions/DatabaseTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public protocol DatabaseTransaction { 8 | associatedtype Database: SwiftDB.Database 9 | 10 | typealias Record = Database.Record 11 | typealias RecordConfiguration = DatabaseRecordConfiguration 12 | typealias RecordUpdate = DatabaseRecordUpdate 13 | 14 | func createRecord(withConfiguration _: RecordConfiguration) throws -> Database.Record 15 | func updateRecord(_ recordID: Record.ID, with update: RecordUpdate) throws 16 | func executeSynchronously(_ request: Database.ZoneQueryRequest) throws -> Database.ZoneQueryRequest.Result 17 | func delete(_: Database.Record.ID) throws 18 | } 19 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/Dump/_DatabaseRecordDump.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CorePersistence 6 | import Swallow 7 | 8 | public struct _DatabaseRecordDump: Codable, Hashable, Identifiable { 9 | public typealias ID = _RecordFieldPayload.PrimaryKeyOrRecordID 10 | 11 | public let id: ID 12 | public let fields: [String: _RecordFieldPayload?] 13 | } 14 | 15 | extension _DatabaseRecordProxy { 16 | public func _dumpRecord() throws -> _DatabaseRecordDump { 17 | .init( 18 | id: try primaryKeyOrRecordID(), 19 | fields: try Dictionary(uniqueKeysWithValues: allKeys.map({ key in 20 | try (key.stringValue, decodeFieldPayload(forKey: key)) 21 | })) 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Container/LocalDatabaseContainer+Initializers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Swallow 7 | 8 | extension LocalDatabaseContainer { 9 | public convenience init(name: String, schema: Schema) throws { 10 | try self.init( 11 | name: name, 12 | schema: schema, 13 | location: nil 14 | ) 15 | } 16 | 17 | public convenience init( 18 | name: String, 19 | schema: Schema, 20 | location: CanonicalFileDirectory, 21 | sqliteFilePath: String 22 | ) throws { 23 | try self.init( 24 | name: name, 25 | schema: schema, 26 | location: location.toURL().appendingPathComponent(sqliteFilePath) 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/Dump/_DatabaseDump.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Combine 6 | import Swallow 7 | import SwiftUIX 8 | 9 | public struct _DatabaseDump: Codable, Hashable { 10 | public var records: [_DatabaseRecordDump] 11 | } 12 | 13 | extension AnyLocalTransaction { 14 | public func _dumpDatabase() throws -> _DatabaseDump { 15 | let instances = try fetchAll() 16 | 17 | var recordDumps: [_DatabaseRecordDump] = [] 18 | 19 | for instance in instances { 20 | let entity = try cast(instance, to: (any Entity).self) 21 | let proxy = try entity._databaseRecordProxy 22 | 23 | try recordDumps.append(proxy._dumpRecord()) 24 | } 25 | 26 | return .init(records: recordDumps) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/_SwiftDB_RuntimeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import XCTest 6 | 7 | @testable import SwiftDB 8 | 9 | final class RuntimeTests: XCTestCase { 10 | let runtime = try! _SwiftDB_DefaultRuntime(schema: _Schema(TestORMSchema())) 11 | 12 | func testEntityLookup() { 13 | XCTAssert(runtime.metatype(forEntityNamed: "EntityWithSimpleRequiredProperty")?.value == TestORMSchema.EntityWithSimpleRequiredProperty.self) 14 | } 15 | 16 | func testEntityKeyPathToStringConversion() async throws { 17 | let fooKeyPath = try runtime.convertEntityKeyPathToString(\TestORMSchema.EntityWithSimpleRequiredProperty.foo) 18 | 19 | XCTAssert(fooKeyPath == "foo") 20 | 21 | XCTAssertThrowsError(try runtime.convertEntityKeyPathToString(\TestORMSchema.EntityWithSimpleRequiredProperty.foo.description)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Modules/_CoreDataPrivate.swiftmodule/arm64e-apple-macos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.2 effective-5.10 (swiftlang-6.2.0.10.905 clang-1700.3.10.902) 3 | // swift-module-flags: -target arm64e-apple-macos26.0 -target-variant arm64e-apple-ios26.0-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -library-level api -enable-upcoming-feature MemberImportVisibility -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -user-module-version 1510 -module-name _CoreDataPrivate 4 | // swift-module-flags-ignorable: -formal-cxx-interoperability-mode=off -interface-compiler-version 6.2 5 | @_exported import _CoreDataPrivate 6 | @_exported import CoreData 7 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | LibraryIdentifier 9 | macos-arm64e-arm64-x86_64 10 | LibraryPath 11 | _CoreDataPrivate.framework 12 | SupportedArchitectures 13 | 14 | arm64 15 | arm64e 16 | x86_64 17 | 18 | SupportedPlatform 19 | macos 20 | 21 | 22 | CFBundlePackageType 23 | XFWK 24 | XCFrameworkFormatVersion 25 | 1.0 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_CoreDataPrivate.framework/Versions/A/Modules/_CoreDataPrivate.swiftmodule/arm64e-apple-macos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.2 effective-5.10 (swiftlang-6.2.0.10.905 clang-1700.3.10.902) 3 | // swift-module-flags: -target arm64e-apple-macos26.0 -target-variant arm64e-apple-ios26.0-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -library-level api -enable-upcoming-feature MemberImportVisibility -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -user-module-version 1510 -module-name _CoreDataPrivate 4 | // swift-module-flags-ignorable: -formal-cxx-interoperability-mode=off -interface-compiler-version 6.2 5 | @_exported import _CoreDataPrivate 6 | @_exported import CoreData 7 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AvailableLibraries 6 | 7 | 8 | LibraryIdentifier 9 | macos-arm64e-arm64-x86_64 10 | LibraryPath 11 | _SwiftDataPrivate.framework 12 | SupportedArchitectures 13 | 14 | arm64 15 | arm64e 16 | x86_64 17 | 18 | SupportedPlatform 19 | macos 20 | 21 | 22 | CFBundlePackageType 23 | XFWK 24 | XCFrameworkFormatVersion 25 | 1.0 26 | 27 | 28 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Runtime/_AnyDatabaseTaskRuntime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | final class _AnyDatabaseTaskRuntime: _SwiftDB_TaskRuntime { 8 | let id = _SwiftDB_TaskRuntimeID() 9 | 10 | private let base: AnyDatabaseTransaction 11 | private let databaseContext: AnyDatabase.Context 12 | 13 | init( 14 | base: AnyDatabaseTransaction, 15 | databaseContext: AnyDatabase.Context 16 | ) { 17 | self.base = base 18 | self.databaseContext = databaseContext 19 | } 20 | 21 | func commit() async throws { 22 | 23 | } 24 | 25 | func scope(_ operation: (_SwiftDB_TaskContext) throws -> T) throws -> T { 26 | try _withSwiftDBTaskContext(.init(databaseContext: databaseContext, _taskRuntime: self)) { context in 27 | try operation(context) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Foundation/Entity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Swallow 7 | 8 | /// An entity in a data schema. 9 | public protocol Entity: _opaque_Entity, PredicateExpressionPrimitiveConvertible, Sendable { 10 | typealias Attribute = _EntityAttribute 11 | typealias Relationship = EntityRelationship 12 | } 13 | 14 | // MARK: - Implementation 15 | 16 | extension Entity { 17 | public func toPredicateExpressionPrimitive() -> PredicateExpressionPrimitive { 18 | do { 19 | return try _databaseRecordProxy.recordID._cast( 20 | to: PredicateExpressionPrimitiveConvertible.self 21 | ) 22 | .toPredicateExpressionPrimitive() 23 | } catch { 24 | assertionFailure(error) 25 | 26 | return NilPredicateExpressionValue(nilLiteral: ()) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseQuerySubscription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | public final class AnyDatabaseQuerySubscription: DatabaseQuerySubscription { 9 | public typealias Database = AnyDatabase 10 | 11 | public typealias Output = [Database.Record] 12 | public typealias Failure = Swift.Error 13 | 14 | private let base: AnyPublisher<[Database.Record], Swift.Error> 15 | 16 | public init(erasing publisher: Publisher) { 17 | self.base = publisher 18 | .eraseError() 19 | .map { 20 | $0.map({ AnyDatabaseRecord(erasing: $0) }) 21 | } 22 | .eraseToAnyPublisher() 23 | } 24 | 25 | public func receive( 26 | subscriber: S 27 | ) where S.Input == Output, S.Failure == Failure { 28 | base.receive(subscriber: subscriber) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2019 Vatsal Manot 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Transactions/DatabaseTransactionExecutor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | /// A write transaction executor. 9 | /// 10 | /// Inspiration: 11 | /// - https://github.com/groue/GRDB.swift#transactions-and-savepoints 12 | /// - https://mongodb.com/docs/realm/sdk/swift/crud/create/ 13 | public protocol DatabaseTransactionExecutor { 14 | associatedtype Transaction: DatabaseTransaction 15 | 16 | typealias Database = Transaction.Database 17 | 18 | /// Asynchronously executes a database transaction. 19 | func execute( 20 | _ body: @escaping (Transaction) throws -> R 21 | ) async throws -> R 22 | 23 | func execute( 24 | queryRequest: Database.ZoneQueryRequest, 25 | _ body: @escaping (Database.ZoneQueryRequest.Result) throws -> R 26 | ) async throws -> R 27 | 28 | /// Synchronously executes a database transaction. 29 | func executeSynchronously( 30 | _ body: @escaping (Transaction) throws -> R 31 | ) throws -> R 32 | } 33 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/DatabaseRecordConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct DatabaseRecordConfiguration { 8 | public let recordType: Database.Record.RecordType? 9 | public let recordID: Database.Record.ID? 10 | public let zone: Database.Zone? 11 | 12 | public init( 13 | recordType: Database.Record.RecordType?, 14 | recordID: Database.Record.ID?, 15 | zone: Database.Zone? 16 | ) { 17 | self.recordType = recordType 18 | self.recordID = recordID 19 | self.zone = zone 20 | } 21 | } 22 | 23 | extension DatabaseRecordConfiguration where Database == AnyDatabase { 24 | func _cast( 25 | to other: DatabaseRecordConfiguration.Type 26 | ) throws -> DatabaseRecordConfiguration { 27 | .init( 28 | recordType: try recordType?._cast(to: T.Record.RecordType.self), 29 | recordID: try recordID.map({ try $0._cast(to: T.Record.ID.self) }), 30 | zone: try zone.map({ try cast($0.base, to: T.Zone.self) }) 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "scale" : "1x", 36 | "size" : "256x256" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "scale" : "2x", 41 | "size" : "256x256" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "scale" : "1x", 46 | "size" : "512x512" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "scale" : "2x", 51 | "size" : "512x512" 52 | } 53 | ], 54 | "info" : { 55 | "author" : "xcode", 56 | "version" : 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseSchemaAdaptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | import Merge 7 | 8 | public struct AnyDatabaseSchemaAdaptor: DatabaseSchemaAdaptor { 9 | public typealias Database = AnyDatabase 10 | 11 | private let base: any DatabaseSchemaAdaptor 12 | 13 | public init(erasing adaptor: Adaptor) { 14 | self.base = adaptor 15 | } 16 | 17 | public func recordType( 18 | for entity: _Schema.Entity.ID? 19 | ) throws -> AnyDatabaseRecord.RecordType { 20 | try AnyDatabaseRecord.RecordType(erasing: base.recordType(for: entity)) 21 | } 22 | 23 | public func entity( 24 | forRecordType recordType: AnyDatabaseRecord.RecordType 25 | ) throws -> _Schema.Entity.ID? { 26 | try base._opaque_entity(forRecordType: recordType) 27 | } 28 | } 29 | 30 | // MARK: - Auxiliary 31 | 32 | extension DatabaseSchemaAdaptor { 33 | public func _opaque_entity( 34 | forRecordType recordType: AnyDatabaseRecord.RecordType 35 | ) throws -> _Schema.Entity.ID? { 36 | try entity(forRecordType: recordType._cast(to: Database.RecordSpace.Record.RecordType.self)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/RecordSnapshot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Swallow 7 | import SwallowMacrosClient 8 | 9 | public struct RecordInstanceMetadata { 10 | public let recordID: AnyDatabaseRecord.ID 11 | 12 | public static func from(instance: Any) throws -> Self { 13 | if let instance = instance as? (any Entity) { 14 | return try .init( 15 | recordID: instance._databaseRecordProxy.recordID 16 | ) 17 | } else { 18 | #throw 19 | } 20 | } 21 | } 22 | 23 | @dynamicMemberLookup 24 | public struct RecordSnapshot { 25 | fileprivate let base: T 26 | 27 | let instanceMetadata: RecordInstanceMetadata 28 | 29 | init(from instance: T) throws { 30 | self.base = instance 31 | self.instanceMetadata = try .from(instance: instance) 32 | } 33 | 34 | public subscript(dynamicMember keyPath: KeyPath) -> Value { 35 | base[keyPath: keyPath] 36 | } 37 | } 38 | 39 | extension RecordSnapshot where T: PredicateExpressionPrimitiveConvertible { 40 | public func toPredicateExpressionPrimitive() -> PredicateExpressionPrimitive { 41 | base.toPredicateExpressionPrimitive() 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Container/AnyDatabaseContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | /// A type-erased database container. 9 | /// 10 | /// Use this type to propagate a reference to your database container in your SwiftUI hierarchy. 11 | public class AnyDatabaseContainer: CustomReflectable, Logging, ObservableObject, @unchecked Sendable { 12 | public enum Status: String, CustomStringConvertible { 13 | case uninitialized 14 | case initializing 15 | case initialized 16 | case deinitializing 17 | case migrationCheckFailed 18 | case migrationRequired 19 | 20 | public var description: String { 21 | rawValue 22 | } 23 | } 24 | 25 | public private(set) var liveAccess = LiveAccess() 26 | 27 | public var customMirror: Mirror { 28 | Mirror(self, children: []) 29 | } 30 | 31 | @Published internal(set) public var status: Status = .uninitialized 32 | 33 | public func load() async throws { 34 | fatalError(.abstract) 35 | } 36 | 37 | public func transact( 38 | _ body: @escaping @Sendable (AnyLocalTransaction) throws -> R 39 | ) async throws -> R { 40 | fatalError(.abstract) 41 | } 42 | 43 | public func reset() async throws { 44 | fatalError(.abstract) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Foundation/DatabaseContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | /// A context that encapsulates the following: 9 | /// - The active SwiftDB runtime. 10 | /// - The active schema. 11 | /// - The active schema adaptor. 12 | public struct DatabaseContext { 13 | public let runtime: _SwiftDB_Runtime 14 | public let schema: _Schema 15 | public let schemaAdaptor: Database.SchemaAdaptor 16 | 17 | public init( 18 | runtime: _SwiftDB_Runtime, 19 | schema: _Schema, 20 | schemaAdaptor: Database.SchemaAdaptor 21 | ) { 22 | self.runtime = runtime 23 | self.schema = schema 24 | self.schemaAdaptor = schemaAdaptor 25 | } 26 | 27 | public func recordSchema( 28 | forRecordType recordType: Database.Record.RecordType 29 | ) throws -> _Schema.Record? { 30 | guard let recordSchemaID = try schemaAdaptor.entity(forRecordType: recordType) else { 31 | return nil 32 | } 33 | 34 | return schema[recordSchemaID] 35 | } 36 | } 37 | 38 | extension DatabaseContext { 39 | public func eraseToAnyDatabaseContext() -> DatabaseContext { 40 | .init( 41 | runtime: runtime, 42 | schema: schema, 43 | schemaAdaptor: .init(erasing: schemaAdaptor) 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/DatabaseRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | import Swift 8 | 9 | /// A database record. 10 | public protocol DatabaseRecord: Identifiable, _CancellablesProviding { 11 | associatedtype Database: SwiftDB.Database where Database.Record == Self 12 | associatedtype RecordType: Codable & Hashable & LosslessStringConvertible 13 | 14 | typealias RelatedRecordIdentifiers = RelatedDatabaseRecordIdentifiers 15 | 16 | var recordType: RecordType { get } 17 | 18 | var allReservedKeys: [AnyCodingKey] { get } 19 | var allKeys: [AnyCodingKey] { get } 20 | 21 | /// Returns a Boolean value that indicates whether a key is known to be supported by this record. 22 | func containsKey(_ key: AnyCodingKey) throws -> Bool 23 | 24 | /// Returns a Boolean value that indicates whether an encoded value is present for the given key. 25 | func containsValue(forKey key: AnyCodingKey) -> Bool 26 | 27 | /// Decodes a value of the given type for the given key. 28 | /// 29 | /// - parameter type: The type value to decode. 30 | /// - parameter key: The key that the value is associated with. 31 | func decode(_ type: Value.Type, forKey key: AnyCodingKey) throws -> Value 32 | 33 | func decodeRelationship( 34 | ofType type: DatabaseRecordRelationshipType, 35 | forKey key: AnyCodingKey 36 | ) throws -> RelatedRecordIdentifiers 37 | } 38 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Runtime/_SwiftDB_TaskLocalValues.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | private struct _SwiftDB_TaskLocalValues { 8 | @TaskLocal static var transactionContext: _SwiftDB_TaskContext? 9 | } 10 | 11 | func _withSwiftDBTaskContext( 12 | _ context: _SwiftDB_TaskContext? = nil, 13 | perform operation: (_SwiftDB_TaskContext) throws -> T 14 | ) throws -> T { 15 | if let context = context { 16 | if let existingContext = _SwiftDB_TaskLocalValues.transactionContext, let transactionInterposer = existingContext._taskRuntime as? any _SwiftDB_TaskRuntimeInterposer { 17 | if transactionInterposer.interposee == (try context._taskRuntime.unwrap()).id { 18 | let newContext = _SwiftDB_TaskContext( 19 | databaseContext: existingContext.databaseContext, 20 | _taskRuntime: transactionInterposer 21 | ) 22 | 23 | return try _SwiftDB_TaskLocalValues.$transactionContext.withValue(newContext) { 24 | try operation(newContext) 25 | } 26 | } else { 27 | fatalError() // FIXME 28 | } 29 | } else { 30 | return try _SwiftDB_TaskLocalValues.$transactionContext.withValue(context) { 31 | try operation(context) 32 | } 33 | } 34 | } else { 35 | return try operation(try _SwiftDB_TaskLocalValues.transactionContext.unwrap()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/PrimitiveAttributeDataType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Foundation 6 | import Swallow 7 | 8 | public protocol PrimitiveAttributeDataType: Codable, Hashable { 9 | 10 | } 11 | 12 | // MARK: - Conditional Conformances 13 | 14 | extension Optional: PrimitiveAttributeDataType where Wrapped: PrimitiveAttributeDataType { 15 | 16 | } 17 | 18 | extension RawRepresentable where RawValue: PrimitiveAttributeDataType { 19 | 20 | } 21 | 22 | // MARK: - Conformances 23 | 24 | extension Bool: PrimitiveAttributeDataType { 25 | 26 | } 27 | 28 | extension Character: PrimitiveAttributeDataType { 29 | 30 | } 31 | 32 | extension Date: PrimitiveAttributeDataType { 33 | 34 | } 35 | 36 | extension Data: PrimitiveAttributeDataType { 37 | 38 | } 39 | 40 | extension Decimal: PrimitiveAttributeDataType { 41 | 42 | } 43 | 44 | extension Double: PrimitiveAttributeDataType { 45 | 46 | } 47 | 48 | extension Float: PrimitiveAttributeDataType { 49 | 50 | } 51 | 52 | extension Int: PrimitiveAttributeDataType { 53 | 54 | } 55 | 56 | extension Int16: PrimitiveAttributeDataType { 57 | 58 | } 59 | 60 | extension Int32: PrimitiveAttributeDataType { 61 | 62 | } 63 | 64 | extension Int64: PrimitiveAttributeDataType { 65 | 66 | } 67 | 68 | extension String: PrimitiveAttributeDataType { 69 | 70 | } 71 | 72 | extension URL: PrimitiveAttributeDataType { 73 | 74 | } 75 | 76 | extension UUID: PrimitiveAttributeDataType { 77 | 78 | } 79 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseZone.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | /// A type-erased wrapper for a database zone. 8 | public struct AnyDatabaseZone: DatabaseZone { 9 | let base: any DatabaseZone 10 | 11 | public var id: ID { 12 | ID(erasing: base._opaque_zoneID) 13 | } 14 | 15 | init(base zone: T) { 16 | self.base = zone 17 | } 18 | } 19 | 20 | // MARK: - Auxiliary 21 | 22 | extension AnyDatabaseZone { 23 | public struct ID: Codable, Hashable { 24 | let base: Codable & Encodable 25 | 26 | private let hashImpl: (inout Hasher) -> Void 27 | 28 | init(erasing id: ID) { 29 | self.base = id 30 | self.hashImpl = id.hash(into:) 31 | } 32 | 33 | public init(from decoder: Decoder) throws { 34 | throw Never.Reason.unsupported 35 | } 36 | 37 | public func encode(to encoder: Encoder) throws { 38 | try base.encode(to: encoder) 39 | } 40 | 41 | public func hash(into hasher: inout Hasher) { 42 | hashImpl(&hasher) 43 | } 44 | 45 | public static func == (lhs: Self, rhs: Self) -> Bool { 46 | lhs.hashValue == rhs.hashValue 47 | } 48 | } 49 | } 50 | 51 | fileprivate extension DatabaseZone { 52 | var _opaque_zoneID: AnyDatabaseZone.ID { 53 | .init(erasing: id) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Property Wrappers/EntityPropertyAccessor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Foundation 6 | import Merge 7 | import Runtime 8 | import Swallow 9 | 10 | protocol _EntityPropertyAccessor: _opaque_ObservableObject, ObservableObject, PropertyWrapper { 11 | var _runtimeMetadata: _EntityPropertyAccessorRuntimeMetadata { get set } 12 | 13 | var _underlyingRecordProxy: _DatabaseRecordProxy? { get set } 14 | 15 | var name: String? { get set } 16 | 17 | func schema() throws -> _Schema.Entity.Property 18 | func initialize(with _underlyingRecordProxy: _DatabaseRecordProxy) throws 19 | 20 | var wrappedValue: WrappedValue { get } 21 | } 22 | 23 | public protocol EntityPropertyAccessor { 24 | var name: String? { get set } 25 | } 26 | 27 | // MARK: - Extensions 28 | 29 | extension _EntityPropertyAccessor { 30 | var key: AnyCodingKey { 31 | get throws { 32 | try name.map(AnyCodingKey.init(stringValue:)).unwrap() 33 | } 34 | } 35 | } 36 | 37 | // MARK: - Auxiliary 38 | 39 | struct _EntityPropertyAccessorRuntimeMetadata { 40 | let valueType: Any.Type 41 | 42 | var didAccessWrappedValueGetter: Bool = false 43 | 44 | init(valueType: Any.Type, didAccessWrappedValueGetter: Bool = false) { 45 | if valueType == Any.self { 46 | assertionFailure() 47 | } 48 | 49 | self.valueType = valueType 50 | self.didAccessWrappedValueGetter = didAccessWrappedValueGetter 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Protocol Conformances/CoreData/CoreData+DatabaseSchemaAdaptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Compute 6 | import CoreData 7 | import Merge 8 | import Swallow 9 | 10 | extension _CoreData.Database { 11 | public struct SchemaAdaptor: SwiftDB.DatabaseSchemaAdaptor { 12 | public typealias Database = _CoreData.Database 13 | 14 | private let schema: _Schema 15 | private let recordTypesByEntityID: BidirectionalMap<_Schema.Entity.ID, Database.RecordSpace.RecordType> 16 | 17 | init(schema: _Schema) { 18 | self.schema = schema 19 | 20 | var recordTypesByEntityID = BidirectionalMap<_Schema.Entity.ID, Database.RecordSpace.RecordType>() 21 | 22 | for entity in schema.entities { 23 | recordTypesByEntityID[entity.id] = Database.RecordSpace.RecordType(rawValue: entity.name) 24 | } 25 | 26 | self.recordTypesByEntityID = recordTypesByEntityID 27 | } 28 | 29 | public func recordType( 30 | for entity: _Schema.Entity.ID? 31 | ) throws -> _CoreData.DatabaseRecord.RecordType { 32 | try recordTypesByEntityID.value(forKey: entity.unwrap()).unwrap() 33 | } 34 | 35 | public func entity( 36 | forRecordType recordType: _CoreData.DatabaseRecord.RecordType 37 | ) throws -> _Schema.Entity.ID? { 38 | try recordTypesByEntityID.key(forValue: recordType).unwrap() 39 | } 40 | } 41 | } 42 | 43 | extension _CoreData.Database.SchemaAdaptor { 44 | private enum Error: Swift.Error { 45 | case defaultRecordTypeUnavailable 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Property Wrappers/ObservedModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Combine 6 | import CoreData 7 | import Swift 8 | import SwiftAPI 9 | import SwiftUIX 10 | 11 | // A property wrapper type that subscribes to an observable model and invalidates a view whenever the observable model changes. 12 | @propertyWrapper 13 | public struct ObservedModel: DynamicProperty { 14 | @Environment(\.database) var database 15 | 16 | private let initialValue: Model 17 | 18 | public var wrappedValue: Model { 19 | get { 20 | initialValue 21 | } 22 | } 23 | 24 | public var projectedValue: Binding { 25 | fatalError(.unimplemented) 26 | } 27 | 28 | public func update() { 29 | 30 | } 31 | 32 | public init(wrappedValue: Model) { 33 | self.initialValue = wrappedValue 34 | } 35 | } 36 | 37 | public final class UpdatingSnapshot: ObservableObject { 38 | private let querySubscription: QuerySubscription 39 | 40 | @Published private(set) var snapshot: RecordSnapshot 41 | 42 | init( 43 | database: AnyDatabaseContainer.LiveAccess, 44 | snapshot: RecordSnapshot 45 | ) throws { 46 | querySubscription = try database.querySubscription( 47 | for: QueryRequest( 48 | predicate: nil, 49 | sortDescriptors: nil, 50 | fetchLimit: FetchLimit.max(1), 51 | scope: .init( 52 | records: [snapshot.instanceMetadata.recordID] 53 | ) 54 | ) 55 | ) 56 | 57 | self.snapshot = snapshot 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseTransactionExecutor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct AnyDatabaseTransactionExecutor: DatabaseTransactionExecutor { 8 | public typealias Database = AnyDatabase 9 | public typealias Transaction = AnyDatabaseTransaction 10 | 11 | private let base: any DatabaseTransactionExecutor 12 | 13 | public init(erasing base: Executor) { 14 | self.base = base 15 | } 16 | 17 | public func execute( 18 | _ body: @escaping (Transaction) throws -> R 19 | ) async throws -> R { 20 | try await base.execute({ try body(AnyDatabaseTransaction(erasing: $0)) }) 21 | } 22 | 23 | public func execute( 24 | queryRequest: Database.ZoneQueryRequest, 25 | _ body: @escaping (Database.ZoneQueryRequest.Result) throws -> R 26 | ) async throws -> R { 27 | try await base._opaque_execute(queryRequest: queryRequest, body) 28 | } 29 | 30 | public func executeSynchronously( 31 | _ body: @escaping (Transaction) throws -> R 32 | ) throws -> R { 33 | try base.executeSynchronously({ try body(AnyDatabaseTransaction(erasing: $0)) }) 34 | } 35 | } 36 | 37 | // MARK: - Auxiliary 38 | 39 | extension DatabaseTransactionExecutor { 40 | fileprivate func _opaque_execute( 41 | queryRequest: AnyDatabase.ZoneQueryRequest, 42 | _ body: @escaping (AnyDatabase.ZoneQueryRequest.Result) throws -> R 43 | ) async throws -> R { 44 | try await execute(queryRequest: queryRequest._cast(to: Database.ZoneQueryRequest.self)) { 45 | try body(AnyDatabase.ZoneQueryRequest.Result(_erasing: $0)) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CoreData/NSPersistentStore++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swift 7 | 8 | extension NSPersistentStore { 9 | func destroy( 10 | persistentStoreCoordinator coordinator: NSPersistentStoreCoordinator 11 | ) throws { 12 | let url = try self.url.unwrap() 13 | 14 | try coordinator.replacePersistentStore( 15 | at: url, 16 | destinationOptions: nil, 17 | withPersistentStoreFrom: url, 18 | sourceOptions: nil, 19 | ofType: type 20 | ) 21 | 22 | try coordinator.destroyPersistentStore( 23 | at: url, 24 | ofType: type, 25 | options: nil 26 | ) 27 | 28 | if coordinator.persistentStores.contains(self) { 29 | try coordinator.remove(self) 30 | } 31 | 32 | let fileManager = FileManager.default 33 | let fileDeleteCoordinator = NSFileCoordinator(filePresenter: nil) 34 | 35 | fileDeleteCoordinator.coordinate( 36 | writingItemAt: url.deletingLastPathComponent(), 37 | options: .forDeleting, 38 | error: nil, 39 | byAccessor: { url in 40 | if fileManager.fileExists(at: url) { 41 | try? fileManager.removeItem(at: url) 42 | } 43 | 44 | let ckAssetFilesURL = url.deletingLastPathComponent().appendingPathComponent("ckAssetFiles") 45 | 46 | if fileManager.fileExists(at: ckAssetFilesURL) { 47 | try? fileManager.removeItem(at: ckAssetFilesURL) 48 | } 49 | } 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Transactions/AnyTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | import SwiftAPI 7 | 8 | public struct AnyTransaction { 9 | private let _SwiftDB_taskContext: _SwiftDB_TaskContext 10 | private let transaction: AnyDatabaseTransaction 11 | 12 | init( 13 | transaction: AnyDatabaseTransaction, 14 | _SwiftDB_taskContext: _SwiftDB_TaskContext 15 | ) { 16 | self._SwiftDB_taskContext = _SwiftDB_taskContext 17 | self.transaction = transaction 18 | } 19 | 20 | private func scope(_ operation: (_SwiftDB_TaskContext) throws -> T) throws -> T { 21 | try _withSwiftDBTaskContext(_SwiftDB_taskContext) { context in 22 | try operation(context) 23 | } 24 | } 25 | } 26 | 27 | extension AnyTransaction { 28 | public func create(_ entityType: Instance.Type) throws -> Instance { 29 | return try scope { context in 30 | let entity = try context.databaseContext.schema.entity(forModelType: entityType).unwrap() 31 | 32 | let record = try transaction.createRecord( 33 | withConfiguration: .init( 34 | recordType: context.databaseContext.schemaAdaptor.recordType(for: entity.id), 35 | recordID: nil, 36 | zone: nil 37 | ) 38 | ) 39 | 40 | return try _SwiftDB_taskContext.createTransactionScopedInstance( 41 | entityType, 42 | for: record, 43 | transaction: transaction 44 | ) 45 | } 46 | } 47 | 48 | public func delete(_ instance: Instance) throws { 49 | try scope { context in 50 | try transaction.delete(instance._databaseRecordProxy.recordID) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Miscellaneous/Record Space/DatabaseRecordSpace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Merge 7 | import Swallow 8 | import SwiftUI 9 | 10 | /// An space to manipulate and track changes to managed database records. 11 | /// 12 | /// `DatabaseRecordSpace` is inspired from `NSManagedObjectContext`. 13 | public protocol DatabaseRecordSpace: Sendable { 14 | associatedtype Database: SwiftDB.Database 15 | associatedtype Zone: DatabaseZone where Zone == Database.Zone 16 | associatedtype Record: DatabaseRecord where Record == Database.Record 17 | 18 | typealias RecordConfiguration = DatabaseRecordConfiguration 19 | typealias SaveError = DatabaseRecordSpaceSaveError 20 | 21 | /// Create a database record associated with this context. 22 | func createRecord( 23 | withConfiguration _: RecordConfiguration 24 | ) throws -> Record 25 | 26 | /// Mark a record for deletion in this record space. 27 | func delete(_: Record.ID) throws 28 | 29 | /// Execute a zone query request within the zones captured by this record space. 30 | /// 31 | /// - Parameters: 32 | /// - request: The query request to execute. 33 | func execute(_ request: Database.ZoneQueryRequest) -> AnyTask 34 | 35 | /// Save the changes made in this record space. 36 | /// 37 | /// - Returns: A task representing the save operation. 38 | func save() -> AnyTask 39 | } 40 | 41 | // MARK: - Extensions 42 | 43 | extension DatabaseRecordSpace { 44 | public func execute(_ request: Database.ZoneQueryRequest) async throws -> Database.ZoneQueryRequest.Result { 45 | try await execute(request).value 46 | } 47 | 48 | public func save() async throws { 49 | try await save().value 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/Coding/_RecordFieldPayload.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CorePersistence 6 | import Swallow 7 | 8 | public indirect enum _RecordFieldPayload: Codable, Hashable { 9 | case attribute(value: EntityAttributeValue) 10 | case relationship(primaryKeysOrRecordIdentifiers: RelatedPrimaryKeysOrRecordIDs) 11 | 12 | public init(from value: Any) throws { 13 | if let value = value as? _RecordFieldPayloadConvertible { 14 | self = try value._toRecordFieldPayload() 15 | } else { 16 | self = .attribute( 17 | value: .object( 18 | value: _TypeSerializingAnyCodable(try cast(value, to: Codable.self)) 19 | ) 20 | ) 21 | } 22 | } 23 | } 24 | 25 | extension _RecordFieldPayload { 26 | public enum EntityAttributeValue: Codable, Hashable { 27 | case primitive(value: _TypeSerializingAnyCodable) 28 | case array(value: [_TypeSerializingAnyCodable]) 29 | case dictionary(value: [_TypeSerializingAnyCodable: _TypeSerializingAnyCodable]) 30 | case object(value: _TypeSerializingAnyCodable) 31 | } 32 | 33 | public enum PrimaryKeyOrRecordID: Codable, Hashable { 34 | case primaryKey(value: _RecordFieldPayload) 35 | case recordID(value: _TypeSerializingAnyCodable) 36 | 37 | init(from recordID: AnyDatabaseRecord.ID) throws { 38 | self = .recordID(value: _TypeSerializingAnyCodable(try cast(recordID, to: Codable.self))) 39 | } 40 | } 41 | 42 | public enum RelatedPrimaryKeysOrRecordIDs: Codable, Hashable { 43 | case toOne(keyOrIdentifier: PrimaryKeyOrRecordID?) 44 | case toUnorderedMany(keysOrIdentifiers: Set) 45 | case toOrderedMany(keysOrIdentifiers: [PrimaryKeyOrRecordID]) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Runtime/_SwiftDB_TaskRuntime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Foundation 6 | import Runtime 7 | import Swallow 8 | 9 | struct _SwiftDB_TaskRuntimeID: Hashable { 10 | private let rawValue: AnyHashable 11 | 12 | private init(rawValue: AnyHashable) { 13 | self.rawValue = rawValue 14 | } 15 | 16 | public init() { 17 | self.init(rawValue: UUID()) 18 | } 19 | } 20 | 21 | /// An internal representation of a SwiftDB encapsulated database transaction. 22 | protocol _SwiftDB_TaskRuntime: AnyObject, Identifiable where ID == _SwiftDB_TaskRuntimeID { 23 | func scope(_ context: (_SwiftDB_TaskContext) throws -> T) throws -> T 24 | 25 | func _scopeRecordMutation(_ body: () throws -> T) throws -> T 26 | } 27 | 28 | /// A transaction wrapper that interposes another transaction. 29 | /// 30 | /// This is needed to implement special transaction types (such as `_AutoCommittingTransaction`). 31 | /// The runtime uses this to ensure that the interposed transaction is used over the base transacton. 32 | protocol _SwiftDB_TaskRuntimeInterposer: _SwiftDB_TaskRuntime { 33 | var interposee: _SwiftDB_TaskRuntimeID { get } 34 | } 35 | 36 | // MARK: - Implementation 37 | 38 | extension _SwiftDB_TaskRuntime { 39 | public func _scopeRecordMutation(_ body: () throws -> T) throws -> T { 40 | try body() 41 | } 42 | } 43 | 44 | // MARK: - Supplementary 45 | 46 | public final class _SwiftDB_TaskRuntimeLink { 47 | let parentID: _SwiftDB_TaskRuntimeID 48 | 49 | init(from parent: any _SwiftDB_TaskRuntime) { 50 | self.parentID = parent.id 51 | 52 | /*asObjCObject(transaction).keepAlive(ExecuteClosureOnDeinit { [weak self] in 53 | if self != nil { 54 | assertionFailure("Transaction link has outlived transaction.") 55 | } 56 | })*/ 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CoreData/NSManagedObject++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swallow 7 | 8 | extension NSObjectProtocol where Self: NSManagedObject { 9 | func primitiveValueExists(forKey key: String) -> Bool { 10 | primitiveValue(forKey: key) != nil 11 | } 12 | 13 | func willAccessValue(_ keyPath: KeyPath) { 14 | willAccessValue(forKey: keyPath._kvcKeyPathString!) 15 | } 16 | 17 | func didAccessValue(_ keyPath: KeyPath) { 18 | didAccessValue(forKey: keyPath._kvcKeyPathString!) 19 | } 20 | 21 | subscript(primitive keyPath: KeyPath) -> Value? { 22 | get { 23 | primitiveValue(forKey: keyPath._kvcKeyPathString!) as? Value 24 | } set { 25 | setPrimitiveValue(newValue, forKey: keyPath._kvcKeyPathString!) 26 | } 27 | } 28 | 29 | subscript(primitive keyPath: KeyPath, defaultValue defaultValue: @autoclosure () -> Value) -> Value { 30 | get { 31 | primitiveValue(forKey: keyPath._kvcKeyPathString!) as? Value ?? defaultValue() 32 | } set { 33 | setPrimitiveValue(newValue, forKey: keyPath._kvcKeyPathString!) 34 | } 35 | } 36 | 37 | subscript(dynamic keyPath: KeyPath, defaultValue: @autoclosure () -> Value) -> Value { 38 | get { 39 | willAccessValue(keyPath) 40 | 41 | defer { 42 | didAccessValue(keyPath) 43 | } 44 | 45 | return self[primitive: keyPath, defaultValue: defaultValue()] 46 | } 47 | } 48 | } 49 | 50 | extension NSManagedObject { 51 | @nonobjc 52 | public func refreshAndMerge() { 53 | self.managedObjectContext?.refresh(self, mergeChanges: true) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CoreData/NSManagedObjectContext++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swallow 7 | 8 | extension NSManagedObjectContext { 9 | public func perform( 10 | _ action: @escaping () -> T 11 | ) async -> T { 12 | await withCheckedContinuation { continuation in 13 | perform { 14 | continuation.resume(returning: action()) 15 | } 16 | } 17 | } 18 | 19 | public func perform( 20 | _ action: @escaping () throws -> T 21 | ) async throws -> T { 22 | try await withCheckedThrowingContinuation { continuation in 23 | perform { 24 | do { 25 | continuation.resume(returning: try action()) 26 | } catch { 27 | continuation.resume(throwing: error) 28 | } 29 | } 30 | } 31 | } 32 | } 33 | 34 | extension NSManagedObjectContext { 35 | private enum _FetchDeleteError: Error { 36 | case objectIdentifierIsNotPermanent(NSManagedObjectID) 37 | } 38 | 39 | public func object(withPermanentID id: NSManagedObjectID) throws -> NSManagedObject { 40 | guard !id.isTemporaryID else { 41 | throw _FetchDeleteError.objectIdentifierIsNotPermanent(id) 42 | } 43 | 44 | return object(with: id) 45 | } 46 | 47 | public func deleteObject(with id: NSManagedObjectID) throws { 48 | let object = try existingObject(with: id) 49 | 50 | delete(object) 51 | } 52 | 53 | public func deleteObject(withPermanentID id: NSManagedObjectID) throws { 54 | guard !id.isTemporaryID else { 55 | throw _FetchDeleteError.objectIdentifierIsNotPermanent(id) 56 | } 57 | 58 | let object = try existingObject(with: id) 59 | 60 | delete(object) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseRecord.RecordType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | import Merge 7 | 8 | extension AnyDatabaseRecord { 9 | public struct RecordType { 10 | typealias RawValue = any Codable & Hashable & LosslessStringConvertible 11 | 12 | private let rawValue: RawValue 13 | 14 | public var description: String { 15 | rawValue.description 16 | } 17 | 18 | private init(rawValue: RawValue) { 19 | self.rawValue = rawValue 20 | } 21 | 22 | public init(erasing value: any Codable & Hashable & LosslessStringConvertible) { 23 | self.init(rawValue: value) 24 | } 25 | 26 | public init(_ description: String) { 27 | self.rawValue = description 28 | } 29 | 30 | func _cast(to recordType: T.Type) throws -> T { 31 | try (try? cast(rawValue, to: recordType)) ?? (try T(description).unwrap()) 32 | } 33 | } 34 | } 35 | 36 | // MARK: - Conformances 37 | 38 | extension AnyDatabaseRecord.RecordType: Codable { 39 | public init(from decoder: Decoder) throws { 40 | try self.init(from: String(from: decoder)) // FIXME: Should decoding be unavailable? 41 | } 42 | 43 | public func encode(to encoder: Encoder) throws { 44 | try rawValue.encode(to: encoder) 45 | } 46 | } 47 | 48 | extension AnyDatabaseRecord.RecordType: Hashable { 49 | public func hash(into hasher: inout Hasher) { 50 | rawValue.hash(into: &hasher) 51 | } 52 | 53 | public static func == (lhs: Self, rhs: Self) -> Bool { 54 | lhs.rawValue.hashValue == rhs.rawValue.hashValue 55 | } 56 | } 57 | 58 | extension AnyDatabaseRecord.RecordType: LosslessStringConvertible { 59 | public init(from value: T) { 60 | self.rawValue = value.description 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CoreData/_CoreData.PersistentStoreDescription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swift 7 | 8 | extension _CoreData { 9 | /// A description used to create and load a persistent store. 10 | public struct PersistentStoreDescription: Hashable { 11 | public private(set) var type: NSPersistentStoreType 12 | public private(set) var configuration: String? 13 | public private(set) var url: URL? 14 | public private(set) var options: [String: NSObject] = [:] 15 | public private(set) var isReadOnly: Bool = false 16 | public private(set) var timeout: TimeInterval 17 | public private(set) var sqlitePragmas: [String: NSObject] = [:] 18 | public private(set) var shouldAddStoreAsynchronously: Bool = false 19 | public private(set) var shouldMigrateStoreAutomatically: Bool = true 20 | public private(set) var shouldInferMappingModelAutomatically: Bool = true 21 | } 22 | } 23 | 24 | // MARK: - Auxiliary 25 | 26 | extension NSPersistentStoreDescription { 27 | public convenience init(_ description: _CoreData.PersistentStoreDescription) { 28 | self.init() 29 | 30 | type = description.type.rawValue 31 | configuration = description.configuration 32 | url = description.url 33 | 34 | for (key, option) in description.options { 35 | setOption(option, forKey: key) 36 | } 37 | 38 | isReadOnly = description.isReadOnly 39 | timeout = description.timeout 40 | 41 | for (pragma, value) in description.sqlitePragmas { 42 | setValue(value, forPragmaNamed: pragma) 43 | } 44 | 45 | shouldAddStoreAsynchronously = description.shouldAddStoreAsynchronously 46 | shouldMigrateStoreAutomatically = description.shouldMigrateStoreAutomatically 47 | shouldInferMappingModelAutomatically = description.shouldInferMappingModelAutomatically 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/_Schema.Record.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | extension _Schema { 8 | public class Record: Codable, Hashable, PolymorphicDecodable, @unchecked Sendable { 9 | private enum CodingKeys: String, CodingKey { 10 | case type 11 | case name 12 | } 13 | 14 | public var type: RecordType 15 | public var name: String 16 | 17 | public var typeDiscriminator: RecordType { 18 | type 19 | } 20 | 21 | public init(type: RecordType, name: String) { 22 | self.type = type 23 | self.name = name 24 | } 25 | 26 | public required init(from decoder: Decoder) throws { 27 | let container = try decoder.container(keyedBy: CodingKeys.self) 28 | 29 | self.type = try container.decode(forKey: .type) 30 | self.name = try container.decode(forKey: .name) 31 | } 32 | 33 | public func encode(to encoder: Encoder) throws { 34 | var container = encoder.container(keyedBy: CodingKeys.self) 35 | 36 | try container.encode(type, forKey: .type) 37 | try container.encode(name, forKey: .name) 38 | } 39 | 40 | public func hash(into hasher: inout Hasher) { 41 | hasher.combine(type) 42 | hasher.combine(name) 43 | } 44 | 45 | public static func == (lhs: Record, rhs: Record) -> Bool { 46 | lhs.hashValue == rhs.hashValue 47 | } 48 | } 49 | } 50 | 51 | // MARK: - Conformances 52 | 53 | extension _Schema.Record: TypeDiscriminable { 54 | public enum RecordType: String, Codable, Swallow.TypeDiscriminator { 55 | case entity 56 | 57 | public func resolveType() -> Any.Type { 58 | switch self { 59 | case .entity: 60 | return _Schema.Entity.self 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/SwiftDataToolbox/AdoptCoreSpotlight.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | import ObjectiveC.runtime 3 | import Foundation 4 | 5 | public func registerCoreSpotlight( 6 | entityToProperties: [String: [String]], 7 | initializeBlock: () throws -> T 8 | ) rethrows -> T { 9 | let thread = Thread.current 10 | assert(thread.threadDictionary[key] == nil) 11 | thread.threadDictionary[key] = entityToProperties 12 | defer { thread.threadDictionary[key] = nil } 13 | 14 | return try initializeBlock() 15 | } 16 | 17 | fileprivate let key = "vmanot_swiftDB_entitiesToProperties" 18 | 19 | @_cdecl("__vmanot_swiftDB_doNotCall_NSEntityDescription_load") 20 | public func __vmanot_swiftDB_doNotCall_NSEntityDescription_load() { 21 | assert(original == nil) 22 | 23 | let method = class_getInstanceMethod( 24 | NSEntityDescription.self, 25 | #selector(setter: NSEntityDescription.properties) 26 | )! 27 | 28 | original = unsafeBitCast(method_getImplementation(method), to: type(of: original)) 29 | method_setImplementation(method, unsafeBitCast(custom, to: IMP.self)) 30 | } 31 | 32 | fileprivate var original: (@convention(c) ( 33 | _ self: NSEntityDescription, 34 | _ cmd: Selector, 35 | _ properties: [NSPropertyDescription] 36 | ) -> Void)? 37 | 38 | fileprivate let custom: (@convention(c) ( 39 | _ self: NSEntityDescription, 40 | _ cmd: Selector, 41 | _ properties: [NSPropertyDescription] 42 | ) -> Void) = { `self`, cmd, properties in 43 | guard 44 | let name = self.name, 45 | let entitiesToProperties = Thread.current.threadDictionary[key] as? [String: [String]], 46 | let propertyNamesToPatch = entitiesToProperties[name] 47 | else { 48 | original!(self, cmd, properties) 49 | return 50 | } 51 | 52 | for case let attribute as NSAttributeDescription in properties { 53 | if propertyNamesToPatch.contains(attribute.name) { 54 | attribute.isIndexedBySpotlight = true 55 | } 56 | } 57 | 58 | original!(self, cmd, properties) 59 | } 60 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CoreData/NSPersistentStoreCoordinator++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swallow 7 | 8 | struct NSPersistentStoreMetadata { 9 | let persistenceFrameworkVersion: Int 10 | let storeUUID: UUID 11 | let storeModelVersionHashesVersion: Int 12 | var storeModelVersionHashesDigest: String 13 | let storeModelVersionHashes: [String: Data] 14 | let autoVacuumLevel: String 15 | let storeType: String 16 | 17 | init(from metadata: [String: Any]) throws { 18 | persistenceFrameworkVersion = try cast(metadata["NSPersistenceFrameworkVersion"]) 19 | storeUUID = try UUID(uuidString: try cast(metadata["NSStoreUUID"], to: String.self)).unwrap() 20 | storeModelVersionHashesVersion = try cast(metadata["NSStoreModelVersionHashesVersion"]) 21 | // = try cast(metadata["NSStoreModelVersionIdentifiers"]) 22 | storeModelVersionHashesDigest = try cast(metadata["NSStoreModelVersionHashesDigest"]) 23 | storeModelVersionHashes = try cast(metadata["NSStoreModelVersionHashes"]) 24 | autoVacuumLevel = try cast(metadata["_NSAutoVacuumLevel"]) 25 | storeType = try cast(metadata["NSStoreType"]) 26 | } 27 | } 28 | 29 | extension NSPersistentStoreCoordinator { 30 | /// Check whether a store at the given location is compatible with a given object model. 31 | static func isStore( 32 | ofType storeType: String, 33 | at storeURL: URL, 34 | withConfigurationName configurationName: String?, 35 | compatibleWithModel model: NSManagedObjectModel 36 | ) throws -> Bool { 37 | let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: storeType, at: storeURL, options: nil) 38 | 39 | return model.isConfiguration(withName: configurationName, compatibleWithStoreMetadata: metadata) 40 | } 41 | 42 | func destroyAll() throws { 43 | for store in persistentStores { 44 | try store.destroy(persistentStoreCoordinator: self) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Modules/_SwiftDataPrivate.swiftmodule/arm64e-apple-macos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.2 effective-5.10 (swiftlang-6.2.0.10.905 clang-1700.3.10.902) 3 | // swift-module-flags: -target arm64e-apple-macos26.0 -target-variant arm64e-apple-ios26.0-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -library-level api -enable-experimental-feature VariadicGenerics -enable-experimental-feature ExtensionMacros -enable-upcoming-feature InternalImportsByDefault -enable-upcoming-feature MemberImportVisibility -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -user-module-version 115 -module-name _SwiftDataPrivate 4 | // swift-module-flags-ignorable: -formal-cxx-interoperability-mode=off -interface-compiler-version 6.2 5 | @_exported import _SwiftDataPrivate 6 | @_exported import SwiftData 7 | 8 | @_originallyDefinedIn(module: "SwiftData", iOS 17.0) 9 | @_originallyDefinedIn(module: "SwiftData", macOS 14.0) 10 | @_originallyDefinedIn(module: "SwiftData", tvOS 17.0) 11 | @_originallyDefinedIn(module: "SwiftData", watchOS 10.0) 12 | @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) 13 | extension SwiftData.Schema { 14 | public func _coreDataMakeManagedObjectModel() -> Any? 15 | } 16 | 17 | @_originallyDefinedIn(module: "SwiftData", iOS 26.0) 18 | @_originallyDefinedIn(module: "SwiftData", macOS 26.0) 19 | @_originallyDefinedIn(module: "SwiftData", tvOS 26.0) 20 | @_originallyDefinedIn(module: "SwiftData", watchOS 26.0) 21 | @available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, *) 22 | extension SwiftData.ModelContainer { 23 | public var datastoresByIdentifier: [Swift.String : SwiftData.DataStore] { 24 | @_silgen_name("$s9SwiftData14ModelContainerC22datastoresByIdentifierSDySSAA0B5Store_pGvgTj") 25 | get 26 | @_silgen_name("$s9SwiftData14ModelContainerC22datastoresByIdentifierSDySSAA0B5Store_pGvsTj") 27 | set 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework/macos-arm64e-arm64-x86_64/_SwiftDataPrivate.framework/Versions/A/Modules/_SwiftDataPrivate.swiftmodule/arm64e-apple-macos.swiftinterface: -------------------------------------------------------------------------------- 1 | // swift-interface-format-version: 1.0 2 | // swift-compiler-version: Apple Swift version 6.2 effective-5.10 (swiftlang-6.2.0.10.905 clang-1700.3.10.902) 3 | // swift-module-flags: -target arm64e-apple-macos26.0 -target-variant arm64e-apple-ios26.0-macabi -enable-objc-interop -enable-library-evolution -swift-version 5 -enforce-exclusivity=checked -O -library-level api -enable-experimental-feature VariadicGenerics -enable-experimental-feature ExtensionMacros -enable-upcoming-feature InternalImportsByDefault -enable-upcoming-feature MemberImportVisibility -enable-experimental-feature DebugDescriptionMacro -enable-bare-slash-regex -user-module-version 115 -module-name _SwiftDataPrivate 4 | // swift-module-flags-ignorable: -formal-cxx-interoperability-mode=off -interface-compiler-version 6.2 5 | @_exported import _SwiftDataPrivate 6 | @_exported import SwiftData 7 | 8 | @_originallyDefinedIn(module: "SwiftData", iOS 17.0) 9 | @_originallyDefinedIn(module: "SwiftData", macOS 14.0) 10 | @_originallyDefinedIn(module: "SwiftData", tvOS 17.0) 11 | @_originallyDefinedIn(module: "SwiftData", watchOS 10.0) 12 | @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) 13 | extension SwiftData.Schema { 14 | public func _coreDataMakeManagedObjectModel() -> Any? 15 | } 16 | 17 | @_originallyDefinedIn(module: "SwiftData", iOS 26.0) 18 | @_originallyDefinedIn(module: "SwiftData", macOS 26.0) 19 | @_originallyDefinedIn(module: "SwiftData", tvOS 26.0) 20 | @_originallyDefinedIn(module: "SwiftData", watchOS 26.0) 21 | @available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, *) 22 | extension SwiftData.ModelContainer { 23 | public var datastoresByIdentifier: [Swift.String : SwiftData.DataStore] { 24 | @_silgen_name("$s9SwiftData14ModelContainerC22datastoresByIdentifierSDySSAA0B5Store_pGvgTj") 25 | get 26 | @_silgen_name("$s9SwiftData14ModelContainerC22datastoresByIdentifierSDySSAA0B5Store_pGvsTj") 27 | set 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Query/QuerySubscription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Combine 6 | import Merge 7 | import Swallow 8 | 9 | /// A subscription to a database query. 10 | /// 11 | /// This subscription is a publisher that replays the last published element upon subscription. 12 | public final class QuerySubscription: ObservableObject { 13 | private let base: AnyDatabaseQuerySubscription 14 | private var baseSubscription: AnyCancellable? 15 | 16 | private let resultsPublisher = ReplaySubject(bufferSize: 1) 17 | 18 | @Published private(set) public var results: [Model]? 19 | 20 | init( 21 | from base: AnyDatabaseQuerySubscription, 22 | context: _SwiftDB_TaskContext 23 | ) { 24 | self.base = base 25 | 26 | baseSubscription = base 27 | .tryMap { records in 28 | try records.map { record in 29 | try context.createSnapshotInstance(Model.self, for: record) 30 | } 31 | } 32 | .stopExecutionOnError() 33 | .sink { completion in 34 | switch completion { 35 | case .finished: 36 | self.resultsPublisher.send(completion: .finished) 37 | case .failure(let error): 38 | self.resultsPublisher.send(completion: .failure(error)) 39 | } 40 | } receiveValue: { value in 41 | self.resultsPublisher.send(value) 42 | 43 | MainThreadScheduler.shared.schedule { 44 | self.results = value 45 | } 46 | } 47 | } 48 | } 49 | 50 | // MARK: - Conformances 51 | 52 | extension QuerySubscription: Publisher { 53 | public typealias Output = [Model] 54 | public typealias Failure = Swift.Error 55 | 56 | public func receive( 57 | subscriber: S 58 | ) where S.Input == Output, S.Failure == Failure { 59 | resultsPublisher.receive(subscriber: subscriber) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/TestORMSchema.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import XCTest 6 | 7 | @testable import SwiftDB 8 | 9 | struct TestORMSchema: Schema { 10 | var body: Body { 11 | EmptyEntity.self 12 | EntityWithSimpleRequiredProperty.self 13 | EntityWithOptionalProperties.self 14 | EntityWithComplexProperties.self 15 | EntityWithDynamicProperties.self 16 | ChildParentEntity.self 17 | } 18 | } 19 | 20 | extension TestORMSchema { 21 | class EmptyEntity: Entity { 22 | required init() { 23 | 24 | } 25 | } 26 | 27 | class EntityWithSimpleRequiredProperty: Entity { 28 | @Attribute var foo: Int = 0 29 | 30 | required init() { 31 | 32 | } 33 | } 34 | 35 | final class EntityWithOptionalProperties: Entity { 36 | @Attribute var foo: Int? = nil 37 | @Attribute var bar: Date? = nil 38 | @Attribute var baz: String? = nil 39 | 40 | required init() { 41 | 42 | } 43 | } 44 | 45 | final class EntityWithComplexProperties: Entity { 46 | enum Animal: String, Codable, Hashable { 47 | case cat 48 | case dog 49 | case lion 50 | } 51 | 52 | @Attribute var animal: Animal = .cat 53 | 54 | required init() { 55 | 56 | } 57 | } 58 | 59 | final class EntityWithDynamicProperties: Entity { 60 | @Attribute var id: UUID = UUID() 61 | 62 | @Attribute(defaultValue: UUID()) var defaultValueID: UUID 63 | 64 | required init() { 65 | 66 | } 67 | } 68 | 69 | struct ChildParentEntity: Identifiable, Entity { 70 | @Attribute var id: UUID = UUID() 71 | 72 | @Relationship(inverse: \ChildParentEntity.children) var parent: ChildParentEntity? 73 | @Relationship(inverse: \ChildParentEntity.parent) var children: RelatedModels 74 | 75 | init() { 76 | 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Query/QueryRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Swallow 7 | import SwiftAPI 8 | 9 | /// A description of search criteria used to retrieve data from a persistent store. 10 | public struct QueryRequest { 11 | public var predicate: AnyPredicate? 12 | public var sortDescriptors: [AnySortDescriptor]? 13 | public var fetchLimit: FetchLimit? 14 | public var scope: Scope 15 | 16 | @_disfavoredOverload 17 | public init( 18 | predicate: AnyPredicate?, 19 | sortDescriptors: [AnySortDescriptor]?, 20 | fetchLimit: FetchLimit?, 21 | scope: Scope 22 | ) { 23 | self.predicate = predicate 24 | self.sortDescriptors = sortDescriptors 25 | self.fetchLimit = fetchLimit 26 | self.scope = scope 27 | } 28 | 29 | public init( 30 | predicate: CocoaPredicate?, 31 | sortDescriptors: [AnySortDescriptor]?, 32 | fetchLimit: FetchLimit?, 33 | scope: Scope 34 | ) { 35 | self.init( 36 | predicate: predicate.map(AnyPredicate.init), 37 | sortDescriptors: sortDescriptors, 38 | fetchLimit: nil, 39 | scope: scope 40 | ) 41 | } 42 | 43 | public init() { 44 | self.init( 45 | predicate: nil, 46 | sortDescriptors: nil, 47 | fetchLimit: nil, 48 | scope: nil 49 | ) 50 | } 51 | } 52 | 53 | extension QueryRequest { 54 | public struct Scope: ExpressibleByNilLiteral { 55 | public let zones: [AnyDatabase.Zone.ID]? 56 | public let records: [AnyDatabase.Record.ID]? 57 | 58 | public init(zones: [AnyDatabase.Zone.ID]? = nil, records: [AnyDatabase.Record.ID]? = nil) { 59 | self.zones = zones 60 | self.records = records 61 | } 62 | 63 | public init(nilLiteral: ()) { 64 | self.init(zones: nil, records: nil) 65 | } 66 | } 67 | 68 | public struct Output { 69 | public typealias Results = [Model] 70 | 71 | public let results: Results 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Relationships/_EntityRelationshipDestination.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Runtime 6 | import Swallow 7 | import Swift 8 | 9 | public protocol _EntityRelationshipDestination { 10 | static var _destinationEntityType: any Entity.Type { get } 11 | 12 | init(_relationshipPropertyAccessor: EntityPropertyAccessor) throws 13 | } 14 | 15 | public protocol _EntityRelationshipToOneDestination: _EntityRelationshipDestination { 16 | associatedtype _DestinationEntityType: Entity 17 | } 18 | 19 | public protocol _EntityRelationshipToManyDestination: _EntityRelationshipDestination { 20 | associatedtype _DestinationEntityType: Entity 21 | } 22 | 23 | // MARK: - Implementation 24 | 25 | extension _EntityRelationshipDestination { 26 | public static var _destinationEntityType: any Entity.Type { 27 | if let type = self as? any _EntityRelationshipToOneDestination.Type { 28 | return type._toOneDestinationEntityType 29 | } else if let type = self as? any _EntityRelationshipToManyDestination.Type { 30 | return type._toManyDestinationEntityType 31 | } else { 32 | fatalError() 33 | } 34 | } 35 | } 36 | 37 | // MARK: - Extensions 38 | 39 | extension _EntityRelationshipToOneDestination { 40 | fileprivate static var _toOneDestinationEntityType: any Entity.Type { 41 | _DestinationEntityType.self 42 | } 43 | } 44 | 45 | extension _EntityRelationshipToManyDestination { 46 | fileprivate static var _toManyDestinationEntityType: any Entity.Type { 47 | _DestinationEntityType.self 48 | } 49 | } 50 | 51 | // MARK: - Conformees 52 | 53 | extension Optional: _EntityRelationshipDestination { 54 | public init(_relationshipPropertyAccessor: EntityPropertyAccessor) { 55 | self = nil 56 | } 57 | } 58 | 59 | extension Optional: _EntityRelationshipToOneDestination where Wrapped: Entity { 60 | public typealias _DestinationEntityType = Wrapped 61 | } 62 | 63 | extension Optional: _EntityRelationshipToManyDestination where Wrapped: _EntityRelationshipToManyDestination { 64 | public typealias _DestinationEntityType = Wrapped._DestinationEntityType 65 | } 66 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Transactions/AnyLocalTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | import SwiftAPI 7 | 8 | public struct AnyLocalTransaction: LocalDatabaseCRUDQ { 9 | private let _SwiftDB_taskContext: _SwiftDB_TaskContext 10 | private let transaction: AnyDatabaseTransaction 11 | 12 | init( 13 | transaction: AnyDatabaseTransaction, 14 | _SwiftDB_taskContext: _SwiftDB_TaskContext 15 | ) { 16 | self._SwiftDB_taskContext = _SwiftDB_taskContext 17 | self.transaction = transaction 18 | } 19 | 20 | private func scope(_ operation: (_SwiftDB_TaskContext) throws -> T) throws -> T { 21 | try _withSwiftDBTaskContext(_SwiftDB_taskContext) { context in 22 | try operation(context) 23 | } 24 | } 25 | } 26 | 27 | extension AnyLocalTransaction { 28 | public func create(_ entityType: Instance.Type) throws -> Instance { 29 | try scope { context in 30 | try AnyTransaction(transaction: transaction, _SwiftDB_taskContext: context).create(entityType) 31 | } 32 | } 33 | 34 | public func execute( 35 | _ request: QueryRequest 36 | ) throws -> QueryRequest.Output { 37 | return try scope { context in 38 | let results = try transaction.executeSynchronously( 39 | DatabaseZoneQueryRequest( 40 | from: request, 41 | databaseContext: _SwiftDB_taskContext.databaseContext 42 | ) 43 | ) 44 | 45 | return QueryRequest.Output( 46 | results: try results.records?.map { 47 | try context.createTransactionScopedInstance( 48 | Model.self, 49 | for: $0, 50 | transaction: transaction 51 | ) 52 | } ?? [] 53 | ) 54 | } 55 | } 56 | 57 | public func delete(_ instance: Instance) throws { 58 | try scope { context in 59 | try AnyTransaction(transaction: transaction, _SwiftDB_taskContext: context).delete(instance) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/Entity/_Schema.Entity.Attribute.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CorePersistence 6 | import FoundationX 7 | import Swallow 8 | 9 | extension _Schema.Entity { 10 | public struct AttributeConfiguration: Codable, Hashable { 11 | public var type: _CodableSwiftType 12 | public var attributeType: _Schema.Entity.AttributeType 13 | public var traits: [EntityAttributeTrait] 14 | public var defaultValue: AnyCodableOrNSCodingValue? 15 | 16 | public func _resolveSwiftType() throws -> Any.Type { 17 | try (try? type.resolveType()) ?? attributeType._swiftType 18 | } 19 | } 20 | 21 | public final class Attribute: _Schema.Entity.Property, @unchecked Sendable { 22 | private enum CodingKeys: String, CodingKey { 23 | case attributeConfiguration 24 | } 25 | 26 | public let attributeConfiguration: AttributeConfiguration 27 | 28 | public init( 29 | name: String, 30 | propertyConfiguration: PropertyConfiguration, 31 | attributeConfiguration: AttributeConfiguration 32 | ) { 33 | self.attributeConfiguration = attributeConfiguration 34 | 35 | super.init( 36 | type: .attribute, 37 | name: name, 38 | propertyConfiguration: propertyConfiguration 39 | ) 40 | } 41 | 42 | public required init(from decoder: Decoder) throws { 43 | let container = try decoder.container(keyedBy: CodingKeys.self) 44 | 45 | self.attributeConfiguration = try container.decode(forKey: .attributeConfiguration) 46 | 47 | try super.init(from: decoder) 48 | } 49 | 50 | public override func encode(to encoder: Encoder) throws { 51 | try super.encode(to: encoder) 52 | 53 | var container = encoder.container(keyedBy: CodingKeys.self) 54 | 55 | try container.encode(attributeConfiguration, forKey: .attributeConfiguration) 56 | } 57 | 58 | public override func hash(into hasher: inout Hasher) { 59 | super.hash(into: &hasher) 60 | 61 | hasher.combine(attributeConfiguration) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/CRUDQ/LocalDatabaseCRUDQ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Merge 7 | import Swallow 8 | import SwiftAPI 9 | 10 | public protocol LocalDatabaseCRUDQ { 11 | func create(_ entityType: Instance.Type) throws -> Instance 12 | 13 | func execute( 14 | _ request: QueryRequest 15 | ) throws -> QueryRequest.Output 16 | 17 | func delete(_ instance: Instance) throws 18 | } 19 | 20 | extension LocalDatabaseCRUDQ { 21 | public func fetchAll() throws -> [any Entity] { 22 | try execute( 23 | QueryRequest( 24 | predicate: nil, 25 | sortDescriptors: nil, 26 | fetchLimit: nil, 27 | scope: nil 28 | ) 29 | ).results 30 | } 31 | 32 | /// Fetch the first available entity instance. 33 | public func first( 34 | _ type: Instance.Type 35 | ) throws -> Instance? { 36 | try execute( 37 | QueryRequest( 38 | predicate: nil, 39 | sortDescriptors: nil, 40 | fetchLimit: FetchLimit.max(1), 41 | scope: nil 42 | ) 43 | ) 44 | .results.first 45 | } 46 | 47 | /// Fetch the first available entity instance. 48 | public func first( 49 | _ type: Instance.Type = Instance.self, 50 | where predicate: CocoaPredicate 51 | ) throws -> Instance? { 52 | try execute( 53 | QueryRequest( 54 | predicate: predicate, 55 | sortDescriptors: nil, 56 | fetchLimit: FetchLimit.max(1), 57 | scope: nil 58 | ) 59 | ) 60 | .results.first 61 | } 62 | 63 | public func all( 64 | of type: Instance.Type 65 | ) throws -> [Instance] { 66 | try execute( 67 | QueryRequest( 68 | predicate: nil, 69 | sortDescriptors: nil, 70 | fetchLimit: FetchLimit.max(1), 71 | scope: nil 72 | ) 73 | ) 74 | .results 75 | } 76 | 77 | public func deleteAll() throws { 78 | try fetchAll().forEach({ try delete($0) }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CoreData/_CoreData._SwiftDB_NSManagedObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Merge 7 | import Swift 8 | 9 | extension _CoreData { 10 | /// A `NSManagedObject` subclass with modern provisions. 11 | @objc(_SwiftDB_NSManagedObject) 12 | open class _SwiftDB_NSManagedObject: NSManagedObject { 13 | lazy private(set) var cancellables = Cancellables() 14 | 15 | var areInitialAttributesSetup: Bool = false 16 | 17 | open func setupInitialAttributes() { 18 | areInitialAttributesSetup = true 19 | } 20 | 21 | /// Derive and set any calculated attributes. 22 | /// 23 | /// This is typically done for optimization purposes. 24 | open func deriveAttributes() { 25 | 26 | } 27 | 28 | override open func awakeFromInsert() { 29 | super.awakeFromInsert() 30 | 31 | if !areInitialAttributesSetup { 32 | setupInitialAttributes() 33 | } 34 | 35 | deriveAttributes() 36 | } 37 | 38 | override open func awakeFromFetch() { 39 | super.awakeFromFetch() 40 | 41 | deriveAttributes() 42 | } 43 | 44 | override open func willSave() { 45 | super.willSave() 46 | } 47 | 48 | override open func willAccessValue(forKey key: String?) { 49 | super.willAccessValue(forKey: key) 50 | } 51 | 52 | override public func willChangeValue(forKey key: String) { 53 | super.willChangeValue(forKey: key) 54 | 55 | if managedObjectContext != nil { 56 | objectWillChange.send() 57 | } 58 | } 59 | 60 | /// Provide a fallback value for `primitiveValue(forKey:)`. 61 | open func primitiveDefaultValue(forKey key: String) -> Any? { 62 | return nil 63 | } 64 | 65 | override open func primitiveValue(forKey key: String) -> Any? { 66 | if let result = super.primitiveValue(forKey: key) { 67 | return result 68 | } else if let result = primitiveDefaultValue(forKey: key) { 69 | return result 70 | } else { 71 | return nil 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Tests/NSPersistentContainerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import XCTest 6 | 7 | @testable import SwiftDB 8 | 9 | final class NSPersistentContainerTests: XCTestCase { 10 | func testLoadPersistentStores() async throws { 11 | let container = NSPersistentContainer( 12 | name: "Test", 13 | managedObjectModel: NSManagedObjectModel() 14 | ) 15 | 16 | let description1 = NSPersistentStoreDescription(url: Self.randomStoreURL()) 17 | description1.shouldAddStoreAsynchronously = true 18 | description1.type = NSSQLiteStoreType 19 | 20 | let description2 = NSPersistentStoreDescription(url: Self.randomStoreURL()) 21 | description2.shouldAddStoreAsynchronously = true 22 | description2.type = NSSQLiteStoreType 23 | 24 | container.persistentStoreDescriptions = [description1, description2] 25 | 26 | for await result in container.loadPersistentStores() { 27 | if let error = result.error { 28 | throw error 29 | } 30 | } 31 | 32 | XCTAssertEqual(container.persistentStoreCoordinator.persistentStores.count, 2) 33 | } 34 | 35 | func testLoadPersistentStoresWithError() async throws { 36 | let container = NSPersistentContainer( 37 | name: "Test", 38 | managedObjectModel: NSManagedObjectModel() 39 | ) 40 | 41 | let description = NSPersistentStoreDescription(url: URL(filePath: "/boo")) 42 | description.shouldAddStoreAsynchronously = true 43 | description.type = NSSQLiteStoreType 44 | description.setOption(false as NSNumber, forKey: NSPersistentHistoryTrackingKey) 45 | 46 | container.persistentStoreDescriptions = [description] 47 | 48 | var didCatch = false 49 | for await result in container.loadPersistentStores() { 50 | if let error = result.error { 51 | didCatch = true 52 | } 53 | } 54 | 55 | XCTAssertTrue(container.persistentStoreCoordinator.persistentStores.isEmpty) 56 | XCTAssertTrue(didCatch) 57 | } 58 | } 59 | 60 | extension NSPersistentContainerTests { 61 | private static func randomStoreURL() -> URL { 62 | let storeURL = FileManager 63 | .default 64 | .temporaryDirectory 65 | .appending(path: UUID().uuidString, directoryHint: .notDirectory) 66 | .appendingPathExtension("sqlite") 67 | 68 | return storeURL 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ------------------------------------------------ 3 | // Original project: SwiftDBTest 4 | // Created on 2025/5/14 by Fatbobman(东坡肘子) 5 | // X: @fatbobman 6 | // Mastodon: @fatbobman@mastodon.social 7 | // GitHub: @fatbobman 8 | // Blog: https://fatbobman.com 9 | // ------------------------------------------------ 10 | // Copyright © 2025-present Fatbobman. All rights reserved. 11 | 12 | import SwiftDB 13 | import SwiftUI 14 | import FoundationX 15 | import Combine 16 | 17 | struct ContentView: View { 18 | 19 | @StateObject var container = try! LocalDatabaseContainer( 20 | name: "mySchema", 21 | schema: MySchema(), 22 | location: URL.documentsDirectory.appending(path: "mydb.sqlite")) 23 | var body: some View { 24 | FooList() 25 | .database(container) 26 | .task { 27 | // try? await container.load() 28 | } 29 | } 30 | } 31 | 32 | #Preview { 33 | ContentView() 34 | } 35 | 36 | struct FooList: View { 37 | @QueryModels() var foos 38 | @Environment(\.database) var container 39 | var body: some View { 40 | VStack { 41 | Button("Add New Foo") { 42 | Task { @MainActor in 43 | try await container.transact { 44 | let foo = try $0.create(Foo.self) 45 | foo.bar = UUID().uuidString 46 | } 47 | } 48 | } 49 | Button("Fetch Foo") { 50 | Task.detached { 51 | try await container.transact { 52 | // let predicate = CocoaPredicate(booleanLiteral: true) 53 | // let predicate = NSPredicate(format: "bar = %@", "8E9EE1D4-473C-49D5-AEEF-4EC64D8DCA4B") 54 | let request = QueryRequest( 55 | predicate: nil,//.init(predicate), 56 | sortDescriptors: [], 57 | fetchLimit: nil, 58 | scope: .init(nilLiteral: ())) 59 | let results = try $0.execute(request) 60 | for result in results.results { 61 | print(result.bar) 62 | } 63 | } 64 | } 65 | } 66 | List(foos){ foo in 67 | Text(foo.bar) 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Protocol Conformances/CoreData/CoreData+DatabaseZone.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import CorePersistence 7 | import FoundationX 8 | import Merge 9 | import Swallow 10 | 11 | extension _CoreData.Database { 12 | public struct Zone: DatabaseZone, Identifiable, Sendable { 13 | public struct ID: Codable, Hashable, Sendable { 14 | let fileURL: URL 15 | 16 | init(_fileURL: URL) { 17 | self.fileURL = _fileURL 18 | } 19 | 20 | init(from store: NSPersistentStore) { 21 | fileURL = store.url! 22 | } 23 | 24 | init(from description: NSPersistentStoreDescription) { 25 | fileURL = description.url! 26 | } 27 | } 28 | 29 | @_UncheckedSendable 30 | var nsPersistentStoreDescription: NSPersistentStoreDescription 31 | 32 | public let id: ID 33 | public let name: String? 34 | public let fileURL: URL? 35 | 36 | init(persistentStoreDescription: NSPersistentStoreDescription) throws { 37 | let fileURL = persistentStoreDescription.url 38 | 39 | self._nsPersistentStoreDescription = .init(wrappedValue: persistentStoreDescription) 40 | self.id = .init(from: persistentStoreDescription) 41 | self.name = persistentStoreDescription.configuration 42 | self.fileURL = fileURL 43 | } 44 | } 45 | } 46 | 47 | extension _CoreData.Database.Zone: FolderEnclosable { 48 | public var topLevelFileContents: [URL.PathComponent] { 49 | guard let fileURL = fileURL else { 50 | return [] 51 | } 52 | 53 | var result: [URL] = [] 54 | 55 | let externalStorageFolderName = ".\(fileURL.deletingPathExtension().lastPathComponent)_SUPPORT" 56 | 57 | result.append(fileURL.deletingLastPathComponent().appendingPathComponent(".com.apple.mobile_container_manager.metadata.plist")) 58 | result.append(fileURL.deletingPathExtension().appendingPathExtension("sqlite-wal")) 59 | result.append(fileURL.deletingPathExtension().appendingPathExtension("sqlite-shm")) 60 | result.append(fileURL.deletingLastPathComponent().appendingPathComponent(externalStorageFolderName, isDirectory: true)) 61 | result.append(fileURL) 62 | 63 | return result.map({ URL.PathComponent(rawValue: $0.lastPathComponent) }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Property Wrappers/EntityPropertyAccessorModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Foundation 6 | import Swallow 7 | 8 | public protocol EntityPropertyAccessorModifier: EntityPropertyAccessor { 9 | associatedtype EntityPropertyAccessorType: EntityPropertyAccessor 10 | 11 | var base: EntityPropertyAccessorType { get } 12 | } 13 | 14 | // MARK: - Implementation 15 | 16 | extension EntityPropertyAccessorModifier where EntityPropertyAccessorType: _EntityPropertyAccessor { 17 | var _runtimeMetadata: _EntityPropertyAccessorRuntimeMetadata { 18 | get { 19 | base._runtimeMetadata 20 | } set { 21 | base._runtimeMetadata = newValue 22 | } 23 | } 24 | 25 | var _underlyingRecordProxy: _DatabaseRecordProxy? { 26 | get { 27 | base._underlyingRecordProxy 28 | } set { 29 | base._underlyingRecordProxy = newValue 30 | } 31 | } 32 | 33 | var name: String? { 34 | get { 35 | base.name 36 | } set { 37 | base.name = newValue 38 | } 39 | } 40 | 41 | func schema() throws -> _Schema.Entity.Property { 42 | try base.schema() 43 | } 44 | 45 | func initialize(with container: _DatabaseRecordProxy) throws { 46 | try base.initialize(with: container) 47 | } 48 | } 49 | 50 | /*// MARK: - Conformances 51 | 52 | @propertyWrapper 53 | public final class RenamingIdentifier: EntityPropertyAccessorModifier { 54 | public var base: EntityPropertyAccessorType 55 | public let wrappedValue: WrappedValue 56 | 57 | public init( 58 | wrappedValue: EntityPropertyAccessorType, 59 | _ identifier: String 60 | ) where EntityPropertyAccessorType == Attribute, 61 | WrappedValue == EntityPropertyAccessorType 62 | { 63 | self.base = wrappedValue 64 | self.wrappedValue = wrappedValue 65 | 66 | base.propertyConfiguration.renamingIdentifier = identifier 67 | } 68 | 69 | public init( 70 | wrappedValue: WrappedValue, 71 | _ identifier: String 72 | ) where WrappedValue: EntityPropertyAccessorModifier, 73 | WrappedValue.EntityPropertyAccessorType == Attribute, 74 | WrappedValue.EntityPropertyAccessorType == EntityPropertyAccessorType 75 | { 76 | self.base = wrappedValue.base 77 | self.wrappedValue = wrappedValue 78 | 79 | base.propertyConfiguration.renamingIdentifier = identifier 80 | } 81 | } 82 | */ 83 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CoreData/NSPersistentContainer++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Combine 6 | import CoreData 7 | import Runtime 8 | import Swallow 9 | import os 10 | 11 | extension NSPersistentContainer { 12 | /// Create a container with the specified name and managed object model. 13 | convenience init(name: String, managedObjectModel: NSManagedObjectModel?) { 14 | if let managedObjectModel = managedObjectModel { 15 | self.init(name: name, managedObjectModel: managedObjectModel) 16 | } else { 17 | self.init(name: name) 18 | } 19 | } 20 | 21 | /// Loads the persistent stores. 22 | func loadPersistentStores() -> AsyncStream<(description: NSPersistentStoreDescription, error: (any Error)?)> { 23 | let (stream, continuation) = AsyncStream<(description: NSPersistentStoreDescription, error: (any Error)?)>.makeStream() 24 | 25 | guard !persistentStoreDescriptions.isEmpty else { 26 | continuation.finish() 27 | return stream 28 | } 29 | 30 | let lock = OSAllocatedUnfairLock<[NSPersistentStoreDescription]>(initialState: persistentStoreDescriptions) 31 | 32 | self.loadPersistentStores { description, error in 33 | continuation.yield((description, error)) 34 | 35 | lock.withLock { descriptions in 36 | descriptions.removeAll(of: description) 37 | if descriptions.isEmpty { 38 | continuation.finish() 39 | } 40 | } 41 | } 42 | 43 | return stream 44 | } 45 | 46 | func persistentStoreDescription( 47 | for store: NSPersistentStore 48 | ) -> NSPersistentStoreDescription? { 49 | persistentStoreDescriptions.first(where: { store.url == $0.url }) 50 | } 51 | 52 | func persistentStore( 53 | for description: NSPersistentStoreDescription 54 | ) -> NSPersistentStore? { 55 | persistentStoreCoordinator.persistentStores.first(where: { description.configuration == $0.configurationName && description.url == $0.url }) 56 | } 57 | 58 | @discardableResult 59 | func performBackgroundTaskAndSave( 60 | _ closure: @escaping (NSManagedObjectContext) -> Void 61 | ) -> Future { 62 | .init { attemptToFulfill in 63 | self.performBackgroundTask { context in 64 | closure(context) 65 | 66 | do { 67 | attemptToFulfill(.success(try context.save())) 68 | } catch { 69 | attemptToFulfill(.failure(error)) 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/DatabaseRecordUpdate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct DatabaseRecordUpdate { 8 | public let key: AnyCodingKey 9 | public let payload: Payload 10 | } 11 | 12 | extension DatabaseRecordUpdate { 13 | public enum Payload { 14 | public enum Data { 15 | case setValue(Any) 16 | case removeValue 17 | 18 | var value: Any? { 19 | switch self { 20 | case .setValue(let value): 21 | return value 22 | case .removeValue: 23 | return nil 24 | } 25 | } 26 | } 27 | 28 | public enum Relationship { 29 | case set(RelatedDatabaseRecordIdentifiers) 30 | case apply(difference: RelatedDatabaseRecordIdentifiers.Difference) 31 | } 32 | 33 | case data(Data) 34 | case relationship(Relationship) 35 | } 36 | } 37 | 38 | // MARK: - Auxiliary 39 | 40 | extension DatabaseRecordUpdate where Database == AnyDatabase { 41 | func _cast( 42 | to type: DatabaseRecordUpdate.Type 43 | ) throws -> DatabaseRecordUpdate { 44 | .init(key: key, payload: try payload._cast(to: type.Payload.self)) 45 | } 46 | } 47 | 48 | extension DatabaseRecordUpdate.Payload: _AnyDatabaseRuntimeCasting where Database == AnyDatabase { 49 | func _cast( 50 | to type: DatabaseRecordUpdate.Payload.Type 51 | ) throws -> DatabaseRecordUpdate.Payload { 52 | switch self { 53 | case .data(let update): 54 | switch update { 55 | case .setValue(let value): 56 | return .data(.setValue(value)) 57 | case .removeValue: 58 | return .data(.removeValue) 59 | } 60 | case .relationship(let update): 61 | switch update { 62 | case .set(let value): 63 | return .relationship( 64 | .set( 65 | try value._cast(to: RelatedDatabaseRecordIdentifiers.self) 66 | ) 67 | ) 68 | case .apply(let value): 69 | return .relationship( 70 | .apply( 71 | difference: try value._cast(to: RelatedDatabaseRecordIdentifiers.Difference.self) 72 | ) 73 | ) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseTransaction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct AnyDatabaseTransaction: DatabaseTransaction { 8 | public typealias Database = AnyDatabase 9 | 10 | private let base: any DatabaseTransaction 11 | 12 | public init(erasing base: Transaction) { 13 | assert(!(base is AnyDatabaseTransaction)) 14 | 15 | self.base = base 16 | } 17 | 18 | public func createRecord( 19 | withConfiguration configuration: RecordConfiguration 20 | ) throws -> Database.Record { 21 | try base._opaque_createRecord(withConfiguration: configuration) 22 | } 23 | 24 | public func updateRecord( 25 | _ recordID: AnyDatabaseRecord.ID, 26 | with update: RecordUpdate 27 | ) throws { 28 | try base._opaque_updateRecord(recordID, with: update) 29 | } 30 | 31 | public func executeSynchronously( 32 | _ request: AnyDatabase.ZoneQueryRequest 33 | ) throws -> AnyDatabase.ZoneQueryRequest.Result { 34 | try base._opaque_executeSynchronously(request) 35 | } 36 | 37 | public func delete(_ recordID: Database.Record.ID) throws { 38 | try base._opaque_delete(recordID) 39 | } 40 | } 41 | 42 | fileprivate extension DatabaseTransaction { 43 | func _opaque_createRecord( 44 | withConfiguration configuration: AnyDatabaseTransaction.RecordConfiguration 45 | ) throws -> AnyDatabase.Record { 46 | assert(!(self is AnyDatabaseTransaction)) 47 | 48 | let record = try createRecord( 49 | withConfiguration: configuration._cast(to: RecordConfiguration.self) 50 | ) 51 | 52 | return AnyDatabaseRecord(erasing: record) 53 | } 54 | 55 | func _opaque_updateRecord( 56 | _ recordID: AnyDatabaseRecord.ID, 57 | with update: AnyDatabaseTransaction.RecordUpdate 58 | ) throws { 59 | assert(!(self is AnyDatabaseTransaction)) 60 | 61 | try updateRecord( 62 | recordID._cast(to: Database.Record.ID.self), 63 | with: try update._cast(to: RecordUpdate.self) 64 | ) 65 | } 66 | 67 | func _opaque_executeSynchronously( 68 | _ request: AnyDatabase.ZoneQueryRequest 69 | ) throws -> AnyDatabase.ZoneQueryRequest.Result { 70 | assert(!(self is AnyDatabaseTransaction)) 71 | 72 | return .init(_erasing: try executeSynchronously(try request._cast(to: Database.ZoneQueryRequest.self))) 73 | } 74 | 75 | func _opaque_delete(_ recordID: AnyDatabase.Record.ID) throws { 76 | assert(!(self is AnyDatabaseTransaction)) 77 | 78 | return try delete(recordID._cast(to: Database.Record.ID.self)) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "SwiftDB", 7 | platforms: [ 8 | .iOS(.v16), 9 | .macOS(.v13), 10 | .tvOS(.v16), 11 | .watchOS(.v9) 12 | ], 13 | products: [ 14 | .library( 15 | name: "SwiftDB", 16 | targets: [ 17 | "SwiftDB", 18 | ] 19 | ), 20 | .library( 21 | name: "_SwiftDataPrivate", 22 | targets: ["_SwiftDataPrivate"] 23 | ), 24 | .library( 25 | name: "_CoreDataPrivate", 26 | targets: ["_CoreDataPrivate"] 27 | ), 28 | .library( 29 | name: "CoreDataToolbox", 30 | targets: ["CoreDataToolbox"] 31 | ), 32 | .library( 33 | name: "SwiftDataToolbox", 34 | targets: [ 35 | "SwiftDataToolbox", 36 | "_SwiftDataToolboxCShims" 37 | ] 38 | ) 39 | ], 40 | dependencies: [ 41 | .package(url: "https://github.com/vmanot/CorePersistence.git", branch: "main"), 42 | .package(url: "https://github.com/vmanot/Merge.git", branch: "master"), 43 | .package(url: "https://github.com/vmanot/SwiftAPI.git", branch: "master"), 44 | .package(url: "https://github.com/vmanot/Swallow.git", branch: "master"), 45 | .package(url: "https://github.com/SwiftUIX/SwiftUIX.git", branch: "master") 46 | ], 47 | targets: [ 48 | .target( 49 | name: "SwiftDB", 50 | dependencies: [ 51 | "CorePersistence", 52 | "Merge", 53 | "Swallow", 54 | "SwiftAPI", 55 | "SwiftUIX" 56 | ], 57 | path: "Sources/SwiftDB", 58 | swiftSettings: [] 59 | ), 60 | .target( 61 | name: "CoreDataToolbox", 62 | dependencies: [ 63 | .byName(name: "Swallow") 64 | ] 65 | ), 66 | .target( 67 | name: "SwiftDataToolbox" 68 | ), 69 | .target( 70 | name: "_SwiftDataToolboxCShims", 71 | dependencies: [ 72 | .byName(name: "SwiftDataToolbox") 73 | ], 74 | cSettings: [ 75 | .unsafeFlags(["-fno-objc-arc"]) 76 | ] 77 | ), 78 | .binaryTarget( 79 | name: "_SwiftDataPrivate", 80 | path: "Sources/_SwiftDataPrivate/_SwiftDataPrivate.xcframework" 81 | ), 82 | .binaryTarget( 83 | name: "_CoreDataPrivate", 84 | path: "Sources/_CoreDataPrivate/_CoreDataPrivate.xcframework" 85 | ), 86 | .testTarget( 87 | name: "SwiftDBTests", 88 | dependencies: ["SwiftDB"], 89 | path: "Tests" 90 | ) 91 | ] 92 | ) 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftDB [WIP] 2 | 3 | > [!IMPORTANT] 4 | > This package is under development and is being currently rewritten to fully leverage Swift 5.9. 5 | 6 | A modern, type-safe database abstraction layer. SwiftDB aims to be an opinionated DBAL for relational and document-oriented databases. 7 | 8 | ## Get Started 9 | 10 | Define an `Entity`: 11 | 12 | ```swift 13 | struct Foo: Entity, Identifiable { 14 | @Attribute var bar: String = "Untitled" 15 | 16 | var id: some Hashable { 17 | bar 18 | } 19 | } 20 | ``` 21 | 22 | Define a `Schema`: 23 | 24 | ```swift 25 | struct MySchema: Schema { 26 | var entities: Entities { 27 | Foo.self 28 | } 29 | } 30 | ``` 31 | 32 | Create a `ContentView` for your application: 33 | 34 | ```swift 35 | struct ContentView: View { 36 | @StateObject var container = PersistentContainer(MySchema()) 37 | 38 | var body: some View { 39 | Text("Hello World") 40 | } 41 | } 42 | ``` 43 | 44 | Create a list view: 45 | 46 | ```swift 47 | struct ListView: View { 48 | @EnvironmentObject var container: PersistentContainer 49 | 50 | @QueryModels() var models 51 | 52 | var body: some View { 53 | NavigationView { 54 | List(models) { foo in 55 | NavigationLink(destination: EditView(foo: foo)) { 56 | Text(foo.bar) 57 | } 58 | .contextMenu { 59 | Button { 60 | container.delete(foo) 61 | } label: { 62 | Text("Delete") 63 | } 64 | } 65 | } 66 | .navigationBarItems( 67 | trailing: Button { 68 | container.create(Foo.self) 69 | } label: { 70 | Image(systemName: .plusCircleFill) 71 | .imageScale(.large) 72 | } 73 | ) 74 | .navigationBarTitle("A List of Foo") 75 | } 76 | } 77 | 78 | struct EditView: View { 79 | @EnvironmentObject var container: PersistentContainer 80 | 81 | let foo: Foo 82 | 83 | var body: some View { 84 | VStack { 85 | Form { 86 | TextField("Enter a value", text: foo.$bar) { 87 | container.save() 88 | } 89 | } 90 | } 91 | .navigationBarTitle("Edit Foo") 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | Add it to our `ContentView`: 98 | 99 | ```swift 100 | struct ContentView: View { 101 | @StateObject var container = PersistentContainer(MySchema()) 102 | 103 | var body: some View { 104 | ListView() 105 | .databaseContainer(container) 106 | } 107 | } 108 | ``` 109 | 110 | That's it. 111 | -------------------------------------------------------------------------------- /Samples/SwiftDBTest/SwiftDBTest.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "514092aacd9b3b4af7c6488efb2cd4ebe8fe247f5cfb661a001eebf688396352", 3 | "pins" : [ 4 | { 5 | "identity" : "corepersistence", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/vmanot/CorePersistence.git", 8 | "state" : { 9 | "branch" : "main", 10 | "revision" : "a474a6ee932a349ddebaf064bf4b3e91982f87d0" 11 | } 12 | }, 13 | { 14 | "identity" : "merge", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/vmanot/Merge.git", 17 | "state" : { 18 | "branch" : "master", 19 | "revision" : "3a08837f3e32c7112a5cc4fb7b53cb6460e473e8" 20 | } 21 | }, 22 | { 23 | "identity" : "swallow", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/vmanot/Swallow.git", 26 | "state" : { 27 | "branch" : "master", 28 | "revision" : "8ab4269e38cadb93ccc0f3698083c0a2a400a145" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-atomics", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/apple/swift-atomics.git", 35 | "state" : { 36 | "revision" : "cd142fd2f64be2100422d658e7411e39489da985", 37 | "version" : "1.2.0" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-collections", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/apple/swift-collections", 44 | "state" : { 45 | "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", 46 | "version" : "1.1.4" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-syntax", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/swift-precompiled/swift-syntax", 53 | "state" : { 54 | "branch" : "release/6.1", 55 | "revision" : "fc197a24fb2e77609fbe3d94624e36f84d758099" 56 | } 57 | }, 58 | { 59 | "identity" : "swiftapi", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/vmanot/SwiftAPI.git", 62 | "state" : { 63 | "branch" : "master", 64 | "revision" : "3e47cc5f9b0cefe9ed1d0971aff22583bd9ac7b0" 65 | } 66 | }, 67 | { 68 | "identity" : "swiftdb", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/vmanot/SwiftDB.git", 71 | "state" : { 72 | "branch" : "fatbobman/SWIFTDB-2", 73 | "revision" : "e781f31c45b6cecd43c59f0e053d58d640660a9d" 74 | } 75 | }, 76 | { 77 | "identity" : "swiftuix", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/SwiftUIX/SwiftUIX.git", 80 | "state" : { 81 | "branch" : "master", 82 | "revision" : "395a5d03834ab762919d3f1414ff3b4a3ef04688" 83 | } 84 | } 85 | ], 86 | "version" : 3 87 | } 88 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseRecord.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | public final class AnyDatabaseRecord: DatabaseRecord, Identifiable, ObservableObject { 9 | public typealias Database = AnyDatabase 10 | 11 | fileprivate let base: any DatabaseRecord 12 | 13 | public init(erasing record: Record) { 14 | assert(!(record is AnyDatabaseRecord)) 15 | 16 | self.base = record 17 | } 18 | 19 | public convenience init(_ record: AnyDatabaseRecord) { 20 | self.init(erasing: record.base) 21 | } 22 | 23 | public var id: ID { 24 | base._opaque_recordID 25 | } 26 | 27 | public var recordType: RecordType { 28 | .init(erasing: base.recordType) 29 | } 30 | 31 | public var allReservedKeys: [AnyCodingKey] { 32 | base.allReservedKeys 33 | } 34 | 35 | public var allKeys: [AnyCodingKey] { 36 | base.allKeys 37 | } 38 | 39 | public func containsKey(_ key: AnyCodingKey) throws -> Bool { 40 | try base.containsKey(key) 41 | } 42 | 43 | public func containsValue(forKey key: AnyCodingKey) -> Bool { 44 | base.containsValue(forKey: key) 45 | } 46 | 47 | public func decode(_ type: Value.Type, forKey key: AnyCodingKey) throws -> Value { 48 | try base.decode(type, forKey: key) 49 | } 50 | 51 | public func decodeRelationship( 52 | ofType type: DatabaseRecordRelationshipType, 53 | forKey key: AnyCodingKey 54 | ) throws -> RelatedRecordIdentifiers { 55 | try base._opaque_decodeRelationship(ofType: type, forKey: key) 56 | } 57 | } 58 | 59 | // MARK: - Auxiliary 60 | 61 | extension AnyDatabaseRecord { 62 | public struct ID: Hashable { 63 | private let base: AnyHashable 64 | 65 | init(erasing base: T) { 66 | assert(!(base is ObjectIdentifier)) 67 | 68 | self.base = base 69 | } 70 | 71 | public func _cast(to type: T.Type) throws -> T { 72 | try cast(base.base, to: type) 73 | } 74 | } 75 | } 76 | 77 | extension AnyDatabaseRecord { 78 | func _cast(to recordType: Record.Type) throws -> Record { 79 | try cast(base, to: recordType) 80 | } 81 | } 82 | 83 | extension DatabaseRecord { 84 | /// Needed because otherwise the compile resolves the default `ObjectIdentifier` `Identifiable.id` implementation. 85 | var _opaque_recordID: AnyDatabaseRecord.ID { 86 | .init(erasing: id) 87 | } 88 | 89 | func _opaque_decodeRelationship( 90 | ofType type: DatabaseRecordRelationshipType, 91 | forKey key: AnyCodingKey 92 | ) throws -> AnyDatabaseRecord.RelatedRecordIdentifiers { 93 | try .init(erasing: try decodeRelationship(ofType: type, forKey: key)) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Foundation/Database.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | /// A type that represents a database. 9 | /// 10 | /// A SwiftDB database is made up of the following parts: 11 | /// 12 | /// - Configuration: 13 | /// The configuration that initializes the database. 14 | /// - SchemaAdaptor: 15 | /// An adaptor provided by the database to convert SwiftDB schemas to its own schema representation. 16 | /// - State: 17 | /// An encapsulation of any additional metadata stored by the database. 18 | /// - Zone: 19 | /// A representation of a local or remote store. 20 | /// - RecordSpace: 21 | /// An in-memory scratchpad for transacting on managed records. 22 | public protocol Database: Named, Identifiable, Sendable where ID: Codable { 23 | typealias Runtime = _SwiftDB_Runtime 24 | 25 | associatedtype Configuration: Codable 26 | associatedtype State: Codable & Equatable 27 | associatedtype SchemaAdaptor: DatabaseSchemaAdaptor where SchemaAdaptor.Database == Self 28 | associatedtype Zone: DatabaseZone 29 | associatedtype Record: DatabaseRecord 30 | associatedtype TransactionExecutor: DatabaseTransactionExecutor 31 | associatedtype QuerySubscription: DatabaseQuerySubscription where QuerySubscription.Database == Self 32 | associatedtype RecordSpace: DatabaseRecordSpace where RecordSpace.Zone == Zone, RecordSpace.Record == Record 33 | 34 | typealias Transaction = TransactionExecutor.Transaction 35 | typealias Context = DatabaseContext 36 | typealias ZoneQueryRequest = DatabaseZoneQueryRequest 37 | 38 | /// The configuration used to initialize the database. 39 | var configuration: Configuration { get } 40 | 41 | /// A type that encapsulates the database state and additional metadata. 42 | var state: State { get } 43 | 44 | /// The database context. 45 | var context: Context { get } 46 | 47 | init( 48 | runtime: any _SwiftDB_Runtime, 49 | schema: _Schema?, 50 | configuration: Configuration, 51 | state: State? 52 | ) async throws 53 | 54 | @discardableResult 55 | func fetchAllAvailableZones() -> AnyTask<[Zone], Error> 56 | 57 | func querySubscription(for request: ZoneQueryRequest) throws -> QuerySubscription 58 | 59 | func transactionExecutor() throws -> TransactionExecutor 60 | } 61 | 62 | // MARK: - Extensions 63 | 64 | extension Database { 65 | public init( 66 | schema: _Schema?, 67 | configuration: Configuration, 68 | state: State? 69 | ) async throws { 70 | try await self.init( 71 | runtime: _SwiftDB_DefaultRuntime(schema: schema), 72 | schema: schema, 73 | configuration: configuration, 74 | state: state 75 | ) 76 | } 77 | } 78 | 79 | extension Database { 80 | public func fetchAllAvailableZones() async throws -> [Zone] { 81 | try await fetchAllAvailableZones().value 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Extensions/CloudKit/CKError++.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CloudKit 6 | import Merge 7 | import Swallow 8 | 9 | extension CKError { 10 | var isTerminal: Bool { 11 | switch code { 12 | case .internalError: 13 | return true 14 | case .partialFailure: 15 | return false 16 | case .networkUnavailable: 17 | return false 18 | case .networkFailure: 19 | return false 20 | case .badContainer: 21 | return true 22 | case .serviceUnavailable: 23 | return false 24 | case .requestRateLimited: 25 | return false 26 | case .missingEntitlement: 27 | return true 28 | case .notAuthenticated: 29 | return true 30 | case .permissionFailure: 31 | return true 32 | case .unknownItem: 33 | return true 34 | case .invalidArguments: 35 | return true 36 | case .resultsTruncated: 37 | return false 38 | case .serverRecordChanged: 39 | return true 40 | case .serverRejectedRequest: 41 | return true 42 | case .assetFileNotFound: 43 | return true 44 | case .assetFileModified: 45 | return true 46 | case .incompatibleVersion: 47 | return true 48 | case .constraintViolation: 49 | return true 50 | case .operationCancelled: 51 | return false 52 | case .changeTokenExpired: 53 | return true 54 | case .batchRequestFailed: 55 | return false 56 | case .zoneBusy: 57 | return false 58 | case .badDatabase: 59 | return true 60 | case .quotaExceeded: 61 | return false 62 | case .zoneNotFound: 63 | return true 64 | case .limitExceeded: 65 | return true 66 | case .userDeletedZone: 67 | return true 68 | case .tooManyParticipants: 69 | return false 70 | case .alreadyShared: 71 | return true 72 | case .referenceViolation: 73 | return true 74 | case .managedAccountRestricted: 75 | return true 76 | case .participantMayNeedVerification: 77 | return true 78 | case .serverResponseLost: 79 | return false 80 | case .assetNotAvailable: 81 | return false 82 | #if canImport(GroupActivities) 83 | case .accountTemporarilyUnavailable: 84 | return true 85 | #endif 86 | @unknown default: 87 | return false 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CoreData/_CoreData._SwiftDB_NSEntityDescription.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import FoundationX 7 | import Runtime 8 | import Swallow 9 | import SwallowMacrosClient 10 | 11 | extension _CoreData { 12 | @objc(_SwiftDB_NSEntityDescription) 13 | class _SwiftDB_NSEntityDescription: NSEntityDescription, NSSecureCoding { 14 | weak var parent: _SwiftDB_NSEntityDescription? 15 | 16 | var _SwiftDB_propertyDescriptions: [String: _Schema.Entity.Property] = [:] 17 | 18 | @objc(supportsSecureCoding) 19 | static var supportsSecureCoding: Bool { 20 | true 21 | } 22 | 23 | var _SwiftDB_allPropertyDescriptions: [String: _Schema.Entity.Property] { 24 | guard let parent = parent else { 25 | return _SwiftDB_propertyDescriptions 26 | } 27 | 28 | return parent._SwiftDB_allPropertyDescriptions.merging(_SwiftDB_propertyDescriptions, uniquingKeysWith: { x, _ in x }) 29 | } 30 | 31 | public convenience init( 32 | from entity: _Schema.Entity, 33 | in schema: _Schema 34 | ) throws { 35 | self.init() 36 | 37 | name = entity.name 38 | managedObjectClassName = try schema.generateNSManagedObjectClass(for: entity.id).name 39 | properties = try entity.properties.map({ try $0.toNSPropertyDescription() }) 40 | 41 | for property in entity.properties { 42 | _SwiftDB_propertyDescriptions[property.name] = property 43 | } 44 | 45 | subentities = try entity.subentities.map { 46 | let subentity = try _SwiftDB_NSEntityDescription(from: $0, in: schema) 47 | 48 | subentity.parent = self 49 | 50 | return subentity 51 | } 52 | } 53 | } 54 | } 55 | 56 | // MARK: - Auxiliary Implementaton - 57 | 58 | extension _Schema { 59 | fileprivate func generateNSManagedObjectClass( 60 | for entityID: _Schema.Entity.ID 61 | ) throws -> ObjCClass { 62 | let entity = try self[entityID].unwrap() 63 | 64 | let superclass = try entity.parent.map({ try generateNSManagedObjectClass(for: $0) }) ?? ObjCClass(_CoreData._SwiftDB_NSManagedObject.self) 65 | 66 | return ObjCClass( 67 | name: "_SwiftDB_" + entity.name, 68 | superclass: superclass 69 | ) 70 | } 71 | } 72 | 73 | extension _Schema.Entity.Property { 74 | fileprivate func toNSPropertyDescription() throws -> NSPropertyDescription { 75 | switch self { 76 | case let attribute as _Schema.Entity.Attribute: 77 | return try NSAttributeDescription(attribute) 78 | case let relationship as _Schema.Entity.Relationship: 79 | return try NSRelationshipDescription(relationship) 80 | default: 81 | #throw 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CoreData/NSManagedObjectModel+DatabaseSchema.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Runtime 7 | import Swift 8 | import CryptoKit 9 | 10 | extension NSManagedObjectModel { 11 | @usableFromInline 12 | convenience init(_ schema: _Schema) throws { 13 | self.init() 14 | 15 | var relationshipNameToRelationship: [String: NSRelationshipDescription] = [:] 16 | var parentNameToChildrenMap: [String: [_CoreData._SwiftDB_NSEntityDescription]] = [:] 17 | var nameToEntityMap: [String: _CoreData._SwiftDB_NSEntityDescription] = [:] 18 | 19 | for entity in schema.entities { 20 | let description = try _CoreData._SwiftDB_NSEntityDescription(from: entity, in: schema) 21 | 22 | nameToEntityMap[entity.name] = description 23 | 24 | if let parentID = entity.parent, let parent = schema[parentID] { 25 | parentNameToChildrenMap[parent.name, default: []].insert(description) 26 | } 27 | 28 | for property in description.properties { 29 | if let property = property as? NSRelationshipDescription { 30 | relationshipNameToRelationship[property.name] = property 31 | } 32 | } 33 | } 34 | 35 | for (name, entity) in nameToEntityMap { 36 | if let children = parentNameToChildrenMap[name] { 37 | entity.subentities = children.map { 38 | $0.parent = entity 39 | 40 | return $0 41 | } 42 | } 43 | } 44 | 45 | for entity in nameToEntityMap.values { 46 | for property in entity.properties { 47 | if let property = property as? NSRelationshipDescription { 48 | if let _SwiftDB_propertyDescription = entity._SwiftDB_allPropertyDescriptions[property.name] as? _Schema.Entity.Relationship { 49 | 50 | let destinationEntityID = try _SwiftDB_propertyDescription.relationshipConfiguration.destinationEntity.unwrap() 51 | let destinationEntity = try schema[destinationEntityID].unwrap() 52 | 53 | property.destinationEntity = nameToEntityMap[destinationEntity.name] 54 | } else { 55 | assertionFailure() 56 | } 57 | 58 | if let _SwiftDB_propertyDescription = entity._SwiftDB_allPropertyDescriptions[property.name] as? _Schema.Entity.Relationship { 59 | property.inverseRelationship = _SwiftDB_propertyDescription.relationshipConfiguration.inverseRelationshipName.flatMap({ relationshipNameToRelationship[$0] }) 60 | } else { 61 | assertionFailure() 62 | } 63 | } 64 | } 65 | } 66 | 67 | self.entities = .init(nameToEntityMap.values.lazy.map({ $0 as NSEntityDescription })) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "d77d641c6e83e2b88d222a2d3c734f70203e1230562f8f243b657c54d070c990", 3 | "pins" : [ 4 | { 5 | "identity" : "corepersistence", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/vmanot/CorePersistence.git", 8 | "state" : { 9 | "branch" : "main", 10 | "revision" : "85d5849cea3633a7604839c0e82021e1cc1bf73f" 11 | } 12 | }, 13 | { 14 | "identity" : "merge", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/vmanot/Merge.git", 17 | "state" : { 18 | "branch" : "master", 19 | "revision" : "9f2b492513384f5cc26ce487b146723cef361d50" 20 | } 21 | }, 22 | { 23 | "identity" : "swallow", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/vmanot/Swallow.git", 26 | "state" : { 27 | "branch" : "master", 28 | "revision" : "685a3ddfe700105557cf28e9df08a2ea315e5868" 29 | } 30 | }, 31 | { 32 | "identity" : "swift-atomics", 33 | "kind" : "remoteSourceControl", 34 | "location" : "https://github.com/apple/swift-atomics.git", 35 | "state" : { 36 | "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", 37 | "version" : "1.3.0" 38 | } 39 | }, 40 | { 41 | "identity" : "swift-collections", 42 | "kind" : "remoteSourceControl", 43 | "location" : "https://github.com/apple/swift-collections", 44 | "state" : { 45 | "revision" : "c1805596154bb3a265fd91b8ac0c4433b4348fb0", 46 | "version" : "1.2.0" 47 | } 48 | }, 49 | { 50 | "identity" : "swift-subprocess", 51 | "kind" : "remoteSourceControl", 52 | "location" : "https://github.com/preternatural-fork/swift-subprocess.git", 53 | "state" : { 54 | "branch" : "main", 55 | "revision" : "b276db492c0a91ea319bf3774755c4e3e7af8082" 56 | } 57 | }, 58 | { 59 | "identity" : "swift-syntax", 60 | "kind" : "remoteSourceControl", 61 | "location" : "https://github.com/swift-precompiled/swift-syntax", 62 | "state" : { 63 | "branch" : "release/6.1", 64 | "revision" : "fc197a24fb2e77609fbe3d94624e36f84d758099" 65 | } 66 | }, 67 | { 68 | "identity" : "swift-system", 69 | "kind" : "remoteSourceControl", 70 | "location" : "https://github.com/apple/swift-system", 71 | "state" : { 72 | "revision" : "61e4ca4b81b9e09e2ec863b00c340eb13497dac6", 73 | "version" : "1.5.0" 74 | } 75 | }, 76 | { 77 | "identity" : "swiftapi", 78 | "kind" : "remoteSourceControl", 79 | "location" : "https://github.com/vmanot/SwiftAPI.git", 80 | "state" : { 81 | "branch" : "master", 82 | "revision" : "23f0e0c23074ba3d56a5be72494e01fae2035951" 83 | } 84 | }, 85 | { 86 | "identity" : "swiftuix", 87 | "kind" : "remoteSourceControl", 88 | "location" : "https://github.com/SwiftUIX/SwiftUIX.git", 89 | "state" : { 90 | "branch" : "master", 91 | "revision" : "56175334751a03f80d3a650c497b8525237d4e43" 92 | } 93 | } 94 | ], 95 | "version" : 3 96 | } 97 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Migration/UnsafeRecordMigrationDestination.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public struct UnsafeRecordMigrationDestination { 8 | let schemaMappingModel: _SchemaMigrationMapping 9 | let sourceEntity: _Schema.Entity 10 | let destinationEntity: _Schema.Entity 11 | let destinationRecord: AnyDatabaseRecord 12 | let destinationRecordProxy: _DatabaseRecordProxy 13 | 14 | init( 15 | schemaMappingModel: _SchemaMigrationMapping, 16 | sourceEntity: _Schema.Entity, 17 | destinationEntity: _Schema.Entity, 18 | destinationRecord: AnyDatabaseRecord, 19 | destinationRecordProxy: _DatabaseRecordProxy 20 | ) { 21 | self.schemaMappingModel = schemaMappingModel 22 | self.sourceEntity = sourceEntity 23 | self.destinationEntity = destinationEntity 24 | self.destinationRecord = destinationRecord 25 | self.destinationRecordProxy = destinationRecordProxy 26 | 27 | assert(destinationRecord.id == destinationRecordProxy.recordID) 28 | } 29 | 30 | public subscript(key: String) -> Any? { 31 | get { 32 | try! destinationRecordProxy.decodeValue(forKey: AnyCodingKey(stringValue: key)) 33 | } nonmutating set { 34 | try! destinationRecordProxy.encodeValue(newValue, forKey: AnyCodingKey(stringValue: key)) 35 | } 36 | } 37 | 38 | public subscript(attribute: _Schema.Entity.Attribute) -> Any? { 39 | get { 40 | self[attribute.name] 41 | } nonmutating set { 42 | self[attribute.name] = newValue 43 | } 44 | } 45 | 46 | public struct AttributeEnumerationArguments { 47 | public let attribute: _Schema.Entity.Attribute 48 | public let sourceAttribute: _Schema.Entity.Attribute? 49 | } 50 | 51 | /** 52 | Enumerates the all `NSAttributeDescription`s. The `attribute` argument can be used as the subscript key to access and mutate the property. The `sourceAttribute` can be used to access properties from the source `UnsafeSourceObject`. 53 | */ 54 | /// Enumerates over all entity attributes. 55 | public func enumerateAttributes( 56 | _ body: (AttributeEnumerationArguments) throws -> Void 57 | ) throws { 58 | func enumerate( 59 | _ entity: _Schema.Entity, 60 | _ body: (AttributeEnumerationArguments) throws -> Void 61 | ) throws { 62 | if 63 | let parentEntityID = entity.parent, 64 | let parentEntity = schemaMappingModel.destination[parentEntityID] 65 | { 66 | try enumerate(parentEntity, body) 67 | } 68 | 69 | for case let attribute as _Schema.Entity.Attribute in entity.properties { 70 | let sourceAttribute = try self.sourceEntity.properties 71 | .first(where: { $0.name == attribute.name }) 72 | .map({ try cast($0, to: _Schema.Entity.Attribute.self) }) 73 | 74 | try body(.init(attribute: attribute, sourceAttribute: sourceAttribute)) 75 | } 76 | } 77 | 78 | try enumerate(destinationEntity, body) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/Entity/_Schema.Entity.Property.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CorePersistence 6 | import FoundationX 7 | import Swallow 8 | 9 | extension _Schema.Entity { 10 | public struct PropertyConfiguration: Codable, Hashable, Sendable { 11 | public var isOptional: Bool 12 | public var isTransient: Bool = false 13 | public var renamingIdentifier: String? 14 | } 15 | 16 | public class Property: Codable, Hashable, @unchecked Sendable { 17 | public static let version: Version? = "0.0.0" 18 | 19 | public let type: PropertyType 20 | public let name: String 21 | public let propertyConfiguration: PropertyConfiguration 22 | 23 | public var typeDiscriminator: PropertyType { 24 | type 25 | } 26 | 27 | public init( 28 | type: PropertyType, 29 | name: String, 30 | propertyConfiguration: PropertyConfiguration 31 | ) { 32 | self.type = type 33 | self.name = name 34 | self.propertyConfiguration = propertyConfiguration 35 | } 36 | 37 | public required init(from decoder: Decoder) throws { 38 | let container = try decoder.container(keyedBy: CodingKeys.self) 39 | 40 | self.type = try container.decode(forKey: .type) 41 | self.name = try container.decode(forKey: .name) 42 | self.propertyConfiguration = try container.decode(forKey: .propertyConfiguration) 43 | } 44 | 45 | public func encode(to encoder: Encoder) throws { 46 | var container = encoder.container(keyedBy: CodingKeys.self) 47 | 48 | try container.encode(type, forKey: .type) 49 | try container.encode(name, forKey: .name) 50 | try container.encode(propertyConfiguration, forKey: .propertyConfiguration) 51 | } 52 | 53 | public func hash(into hasher: inout Hasher) { 54 | hasher.combine(name) 55 | hasher.combine(propertyConfiguration) 56 | } 57 | } 58 | } 59 | 60 | // MARK: - Conformances 61 | 62 | extension _Schema.Entity.Property: PolymorphicDecodable { 63 | fileprivate enum CodingKeys: String, CodingKey { 64 | case type 65 | case name 66 | case propertyConfiguration 67 | } 68 | } 69 | 70 | extension _Schema.Entity.Property: Comparable { 71 | public static func < (lhs: _Schema.Entity.Property, rhs: _Schema.Entity.Property) -> Bool { 72 | (lhs.propertyConfiguration.renamingIdentifier ?? lhs.name) < (rhs.propertyConfiguration.renamingIdentifier ?? rhs.name) 73 | } 74 | } 75 | 76 | extension _Schema.Entity.Property: Equatable { 77 | public static func == (lhs: _Schema.Entity.Property, rhs: _Schema.Entity.Property) -> Bool { 78 | lhs.hashValue == rhs.hashValue 79 | } 80 | } 81 | 82 | extension _Schema.Entity.Property: TypeDiscriminable { 83 | public enum PropertyType: String, Codable, Swallow.TypeDiscriminator { 84 | case attribute 85 | case relationship 86 | 87 | public func resolveType() -> Any.Type { 88 | switch self { 89 | case .attribute: 90 | return _Schema.Entity.Attribute.self 91 | case .relationship: 92 | return _Schema.Entity.Relationship.self 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/CRUDQ/DatabaseCRUDQ.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Merge 7 | import Swallow 8 | import SwiftAPI 9 | 10 | public protocol DatabaseCRUDQ { 11 | func create(_ entityType: Instance.Type) async throws -> Instance 12 | func queryExecutionTask(for request: QueryRequest) -> AnyTask.Output, Error> 13 | func querySubscription(for request: QueryRequest) async throws -> QuerySubscription 14 | func delete(_ instance: Instance) async throws 15 | func deleteAll() async throws 16 | } 17 | 18 | // MARK: - Extensions 19 | 20 | extension DatabaseCRUDQ { 21 | /// Create an entity instance. 22 | @discardableResult 23 | public func create( 24 | _ type: Instance.Type, 25 | body: (Instance) throws -> Void 26 | ) async throws -> Instance { 27 | let record = try await create(type) 28 | 29 | try body(record) 30 | 31 | return record 32 | } 33 | } 34 | 35 | extension DatabaseCRUDQ { 36 | public func execute( 37 | _ request: QueryRequest 38 | ) async throws -> QueryRequest.Output { 39 | try await queryExecutionTask(for: request).value 40 | } 41 | } 42 | 43 | extension DatabaseCRUDQ { 44 | public func fetchAll() async throws -> [Any] { 45 | try await execute( 46 | QueryRequest( 47 | predicate: nil, 48 | sortDescriptors: nil, 49 | fetchLimit: nil, 50 | scope: nil 51 | ) 52 | ).results 53 | } 54 | 55 | /// Fetch the first available entity instance. 56 | public func first( 57 | _ type: Instance.Type 58 | ) async throws -> Instance? { 59 | try await execute( 60 | QueryRequest( 61 | predicate: nil, 62 | sortDescriptors: nil, 63 | fetchLimit: FetchLimit.max(1), 64 | scope: nil 65 | ) 66 | ) 67 | .results.first 68 | } 69 | 70 | /// Fetch the first available entity instance. 71 | public func first( 72 | _ type: Instance.Type = Instance.self, 73 | where predicate: CocoaPredicate 74 | ) async throws -> Instance? { 75 | try await execute( 76 | QueryRequest( 77 | predicate: predicate, 78 | sortDescriptors: nil, 79 | fetchLimit: FetchLimit.max(1), 80 | scope: nil 81 | ) 82 | ) 83 | .results.first 84 | } 85 | 86 | public func all( 87 | of type: Instance.Type 88 | ) async throws -> [Instance] { 89 | try await execute( 90 | QueryRequest( 91 | predicate: nil, 92 | sortDescriptors: nil, 93 | fetchLimit: FetchLimit.max(1), 94 | scope: nil 95 | ) 96 | ) 97 | .results 98 | } 99 | } 100 | 101 | extension DatabaseCRUDQ { 102 | public func delete( 103 | allOf instances: Instances 104 | ) async throws where Instances.Element: Entity { 105 | for instance in instances { 106 | try await delete(instance) 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Relationships/RelatedModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CoreData 6 | import Swallow 7 | import SwiftUIX 8 | 9 | /// An collection of models related to an entity. 10 | public struct RelatedModels { 11 | private struct _Required { 12 | let recordProxy: _DatabaseRecordProxy 13 | let key: AnyCodingKey 14 | } 15 | 16 | private let _required: _Required? 17 | 18 | private var recordProxy: _DatabaseRecordProxy{ 19 | get throws { 20 | try _required.unwrap().recordProxy 21 | } 22 | } 23 | 24 | private var key: AnyCodingKey { 25 | get throws { 26 | try _required.unwrap().key 27 | } 28 | } 29 | 30 | private var relationship: RelatedDatabaseRecordIdentifiers { 31 | get throws { 32 | try recordProxy.decodeRelationship(forKey: key) 33 | } 34 | } 35 | 36 | public var isEmpty: Bool { 37 | false // FIXME: !!! 38 | } 39 | 40 | private init(_required: _Required?) { 41 | self._required = _required 42 | } 43 | 44 | init( 45 | recordProxy: _DatabaseRecordProxy, 46 | key: AnyCodingKey 47 | ) { 48 | self.init( 49 | _required: .init( 50 | recordProxy: recordProxy, 51 | key: key 52 | ) 53 | ) 54 | } 55 | } 56 | 57 | extension RelatedModels { 58 | public var count: Int { 59 | try! relationship._toCollection().count 60 | } 61 | 62 | public func insert(_ model: Model) { 63 | do { 64 | try recordProxy.decodeAndReencodeRelationship(forKey: key) { relationship in 65 | let metadata = try RecordInstanceMetadata.from(instance: model) 66 | 67 | try relationship.insert(metadata.recordID) 68 | } 69 | } catch { 70 | assertionFailure(error) 71 | } 72 | } 73 | 74 | public func remove(_ model: Model) { 75 | do { 76 | try recordProxy.decodeAndReencodeRelationship(forKey: key) { relationship in 77 | let metadata = try RecordInstanceMetadata.from(instance: model) 78 | 79 | try relationship.remove(metadata.recordID) 80 | } 81 | } catch { 82 | assertionFailure(error) 83 | } 84 | } 85 | } 86 | 87 | // MARK: - Conformances 88 | 89 | extension RelatedModels: _EntityRelationshipToManyDestination { 90 | public typealias _DestinationEntityType = Model 91 | 92 | public static var entityCardinality: _Schema.Entity.Relationship.EntityCardinality { 93 | .many 94 | } 95 | 96 | public init(_relationshipPropertyAccessor: EntityPropertyAccessor) throws { 97 | let accessor = try cast(_relationshipPropertyAccessor, to: (any _EntityPropertyAccessor).self) 98 | 99 | if let recordProxy = accessor._underlyingRecordProxy { 100 | self.init( 101 | _required: _Required( 102 | recordProxy: recordProxy, 103 | key: try accessor.key 104 | ) 105 | ) 106 | } else { 107 | self.init(_required: nil) 108 | } 109 | } 110 | 111 | public static func _uninitializedInstance() -> RelatedModels { 112 | .init(_required: nil) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/Proxy/_DatabaseRecordSnapshot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Diagnostics 6 | import Compute 7 | import CorePersistence 8 | import Swallow 9 | 10 | final class _DatabaseRecordSnapshot: Logging { 11 | let allKeys: [AnyCodingKey] 12 | var attributeValues: [AnyCodingKey: Any] = [:] 13 | var relationships: [AnyCodingKey: RelatedDatabaseRecordIdentifiers] = [:] 14 | 15 | var attributeValuesDiff = DictionaryDifference( 16 | insertions: [], 17 | updates: [], 18 | removals: [] 19 | ) 20 | 21 | init( 22 | from decoder: _DatabaseRecordDataDecoder 23 | ) throws { 24 | self.allKeys = decoder.record.allKeys 25 | 26 | try _decodeAllValuesAndRelationships(from: decoder) 27 | } 28 | 29 | private func _decodeAllValuesAndRelationships( 30 | from decoder: _DatabaseRecordDataDecoder 31 | ) throws { 32 | let recordSchema = try cast(decoder.recordSchema.unwrap(), to: _Schema.Entity.self) 33 | 34 | for attribute in recordSchema.attributes { 35 | let key = AnyCodingKey(stringValue: attribute.name) 36 | 37 | attributeValues[key] = try decoder.decodeValue(forKey: key) 38 | } 39 | 40 | for relationship in recordSchema.relationships { 41 | let key = AnyCodingKey(stringValue: relationship.name) 42 | 43 | relationships[key] = try decoder.decodeRelationship(forKey: key) 44 | } 45 | } 46 | 47 | deinit { 48 | if !attributeValuesDiff.isEmpty { 49 | assertionFailure() 50 | } 51 | } 52 | } 53 | 54 | extension _DatabaseRecordSnapshot: _DatabaseRecordProxyBase { 55 | func containsValue(forKey key: AnyCodingKey) throws -> Bool { 56 | attributeValues.contains(key: AnyCodingKey(erasing: key)) 57 | } 58 | 59 | func decodeValue(_ type: Value.Type, forKey key: AnyCodingKey) throws -> Value { 60 | return try cast(decodeValue(forKey: key), to: Value.self) 61 | } 62 | 63 | func decodeValue(forKey key: AnyCodingKey) throws -> Any? { 64 | attributeValues[key] 65 | } 66 | 67 | func encodeInitialValue(_ value: Value, forKey key: AnyCodingKey) throws { 68 | attributeValues[key] = attributeValues[key] ?? value 69 | } 70 | 71 | func encodeValue(_ value: Value, forKey key: AnyCodingKey) throws { 72 | try encodeValue(cast(value, to: Optional.self), forKey: key) 73 | } 74 | 75 | func encodeValue(_ value: Any?, forKey key: AnyCodingKey) throws { 76 | attributeValues[key] = value 77 | attributeValuesDiff[key] = value 78 | } 79 | 80 | func decodeRelationship( 81 | forKey key: AnyCodingKey 82 | ) throws -> RelatedDatabaseRecordIdentifiers { 83 | let relationship = try relationships[key].unwrap() 84 | 85 | return relationship 86 | } 87 | 88 | func encodeRelationship( 89 | _ relationship: RelatedDatabaseRecordIdentifiers, 90 | forKey key: AnyCodingKey 91 | ) throws { 92 | relationships[key] = relationship 93 | } 94 | 95 | func encodeRelationshipDiff( 96 | _ diff: RelatedDatabaseRecordIdentifiers.Difference, 97 | forKey key: AnyCodingKey 98 | ) throws { 99 | var relationship = try relationships[key].unwrap() 100 | 101 | try relationship.applyUnconditionally(diff) 102 | 103 | relationships[key] = relationship 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabaseRecordSpace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | import Merge 7 | 8 | public final class AnyDatabaseRecordSpace: DatabaseRecordSpace, Sendable { 9 | public typealias Database = AnyDatabase 10 | public typealias Zone = AnyDatabaseZone 11 | public typealias Record = AnyDatabaseRecord 12 | public typealias QuerySubscription = AnyDatabaseQuerySubscription 13 | 14 | private let base: any DatabaseRecordSpace 15 | 16 | private init(base: any DatabaseRecordSpace) { 17 | self.base = base 18 | } 19 | 20 | public convenience init(erasing recordSpace: RecordSpace) { 21 | self.init(base: recordSpace) 22 | } 23 | 24 | public func createRecord( 25 | withConfiguration configuration: RecordConfiguration 26 | ) throws -> AnyDatabaseRecord { 27 | try base._opaque_createRecord( 28 | withConfiguration: configuration 29 | ) 30 | } 31 | 32 | public func execute(_ request: Database.ZoneQueryRequest) -> AnyTask { 33 | base._opaque_execute(request) 34 | } 35 | 36 | public func delete(_ recordID: AnyDatabaseRecord.ID) throws { 37 | try base._opaque_delete(recordID) 38 | } 39 | 40 | @discardableResult 41 | public func save() -> AnyTask { 42 | base._opaque_save() 43 | } 44 | } 45 | 46 | // MARK: - Auxiliary 47 | 48 | private extension DatabaseRecordSpace { 49 | func _opaque_createRecord( 50 | withConfiguration configuration: AnyDatabaseRecordSpace.RecordConfiguration 51 | ) throws -> AnyDatabaseRecord { 52 | let record = try createRecord(withConfiguration: configuration._cast(to: RecordConfiguration.self)) 53 | 54 | return .init(erasing: record) 55 | } 56 | 57 | func _opaque_execute( 58 | _ request: AnyDatabase.ZoneQueryRequest 59 | ) -> AnyTask { 60 | do { 61 | return try execute(request._cast(to: Database.ZoneQueryRequest.self)) 62 | .successPublisher 63 | .map { result in 64 | AnyDatabase.ZoneQueryRequest.Result( 65 | records: result.records?.map({ AnyDatabaseRecord(erasing: $0) }) 66 | ) 67 | } 68 | .convertToTask() 69 | } catch { 70 | return .failure(error) 71 | } 72 | } 73 | 74 | func _opaque_delete(_ record: AnyDatabaseRecord.ID) throws { 75 | let _record = try record._cast(to: Record.ID.self) 76 | 77 | return try delete(_record) 78 | } 79 | 80 | func _opaque_save() -> AnyTask { 81 | save() 82 | .successPublisher 83 | .mapError { error in 84 | switch error { 85 | case .canceled: 86 | return AnyDatabaseRecordSpace.SaveError( 87 | description: "cancelled", 88 | mergeConflicts: [] 89 | ) 90 | case .error(let error): 91 | return AnyDatabaseRecordSpace.SaveError( 92 | description: error.description, 93 | mergeConflicts: error.mergeConflicts?.map({ DatabaseRecordMergeConflict(source: AnyDatabaseRecord(erasing: $0.source)) }) 94 | ) 95 | } 96 | } 97 | .convertToTask() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Schema/_EntityAttributeSchemaRepresentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Foundation 6 | import Swift 7 | 8 | protocol _EntityAttributeSchemaRepresentable { 9 | static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType 10 | } 11 | 12 | // MARK: - Conformances 13 | 14 | extension Bool: _EntityAttributeSchemaRepresentable { 15 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 16 | .primitive(type: .boolean) 17 | } 18 | } 19 | 20 | extension Character: _EntityAttributeSchemaRepresentable { 21 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 22 | .primitive(type: .string) 23 | } 24 | } 25 | 26 | extension Date: _EntityAttributeSchemaRepresentable { 27 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 28 | .primitive(type: .date) 29 | } 30 | } 31 | 32 | extension Data: _EntityAttributeSchemaRepresentable { 33 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 34 | .primitive(type: .binaryData) 35 | } 36 | } 37 | 38 | extension Decimal: _EntityAttributeSchemaRepresentable { 39 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 40 | .primitive(type: .decimal) 41 | } 42 | } 43 | 44 | extension Double: _EntityAttributeSchemaRepresentable { 45 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 46 | .primitive(type: .double) 47 | } 48 | } 49 | 50 | extension Float: _EntityAttributeSchemaRepresentable { 51 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 52 | .primitive(type: .float) 53 | } 54 | } 55 | 56 | extension Int: _EntityAttributeSchemaRepresentable { 57 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 58 | .primitive(type: .integer64) 59 | } 60 | } 61 | 62 | extension Int16: _EntityAttributeSchemaRepresentable { 63 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 64 | .primitive(type: .integer16) 65 | } 66 | } 67 | 68 | extension Int32: _EntityAttributeSchemaRepresentable { 69 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 70 | .primitive(type: .integer32) 71 | } 72 | } 73 | 74 | extension Int64: _EntityAttributeSchemaRepresentable { 75 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 76 | .primitive(type: .integer64) 77 | } 78 | } 79 | 80 | extension String: _EntityAttributeSchemaRepresentable { 81 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 82 | .primitive(type: .string) 83 | } 84 | } 85 | 86 | extension URL: _EntityAttributeSchemaRepresentable { 87 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 88 | .primitive(type: .URI) 89 | } 90 | } 91 | 92 | extension UUID: _EntityAttributeSchemaRepresentable { 93 | public static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 94 | .primitive(type: .UUID) 95 | } 96 | } 97 | 98 | extension Array: _EntityAttributeSchemaRepresentable { 99 | static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 100 | .array(elementType: .init(from: Element.self)) 101 | } 102 | } 103 | 104 | extension Dictionary: _EntityAttributeSchemaRepresentable { 105 | static func toSchemaEntityAttributeType() -> _Schema.Entity.AttributeType { 106 | .dictionary(keyType: .init(from: Key.self), valueType: .init(from: Value.self)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Foundation/_opaque_Entity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Runtime 7 | import Swallow 8 | 9 | /// A shadow protocol for `Entity`. 10 | public protocol _opaque_Entity: Initiable { 11 | /// 对应的父实体类型(如果有的话)。 12 | static var _opaque_ParentEntity: (any Entity.Type)? { get } 13 | } 14 | 15 | extension _opaque_Entity where Self: Entity { 16 | public typealias RelatableEntityType = Self 17 | } 18 | 19 | // MARK: - Implementation 20 | 21 | extension _opaque_Entity { 22 | // 获取所有属性访问器。 23 | var _runtime_propertyAccessors: [any _EntityPropertyAccessor] { 24 | InstanceMirror(self)!.allChildren.compactMap { key, value in 25 | (value as? any _EntityPropertyAccessor) 26 | } 27 | } 28 | 29 | // 配置属性访问器。 30 | // 该方法会遍历所有属性访问器,并为每个访问器设置名称和记录代理。 31 | mutating func _runtime_configurePropertyAccessors( 32 | withRecordProxy recordProxy: _DatabaseRecordProxy? 33 | ) throws { 34 | var instance = InstanceMirror(self)! 35 | 36 | for (key, value) in instance.allChildren { 37 | if let property = value as? any _EntityPropertyAccessor { 38 | if property.name == nil { 39 | property.name = .init(key.stringValue.dropPrefixIfPresent("_")) 40 | } 41 | 42 | if let recordProxy = recordProxy { 43 | try property.initialize(with: recordProxy) 44 | } 45 | 46 | instance[key] = property 47 | } 48 | } 49 | 50 | self = try cast(instance.subject, to: Self.self) 51 | } 52 | 53 | init(_databaseRecordProxy: _DatabaseRecordProxy?) throws { 54 | self.init() 55 | 56 | try _runtime_configurePropertyAccessors(withRecordProxy: _databaseRecordProxy) 57 | } 58 | } 59 | 60 | extension _opaque_Entity where Self: Entity { 61 | public static var _opaque_ParentEntity: (any Entity.Type)? { 62 | return nil 63 | } 64 | 65 | var _databaseRecordProxy: _DatabaseRecordProxy { 66 | get throws { 67 | for (_, value) in InstanceMirror(self)!.allChildren { 68 | if let value = value as? any _EntityPropertyAccessor { 69 | if let proxy = value._underlyingRecordProxy { 70 | return proxy 71 | } 72 | } 73 | } 74 | 75 | throw _opaque_EntityError.failedToResolveDatabaseRecordProxy 76 | } 77 | } 78 | } 79 | 80 | extension _opaque_Entity where Self: Entity & AnyObject { 81 | public static var _opaque_ParentEntity: (any Entity.Type)? { 82 | ObjCClass(Self.self).superclass?.value as? any Entity.Type 83 | } 84 | } 85 | 86 | extension _opaque_Entity where Self: Entity & ObservableObject { 87 | public static var _opaque_ParentEntity: (any Entity.Type)? { 88 | ObjCClass(Self.self).superclass?.value as? any Entity.Type 89 | } 90 | } 91 | 92 | extension _opaque_Entity where Self: Entity & Identifiable { 93 | public var _opaque_id: AnyHashable? { 94 | AnyHashable(id) 95 | } 96 | } 97 | 98 | // MARK: - Auxiliary 99 | 100 | extension _opaque_Entity { 101 | /// 检查当前实体是否是另一个实体的父类。 102 | public static func isSuperclass(of other: _opaque_Entity.Type) -> Bool { 103 | if other == Self.self { 104 | return false 105 | } else if other is Self.Type { // 检查 other 是否可以被转换为 Self.Type 106 | return true 107 | } else { 108 | return false 109 | } 110 | } 111 | } 112 | 113 | fileprivate enum _opaque_EntityError: _SwiftDB_Error { 114 | case failedToResolveDatabaseRecordProxy 115 | } 116 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/Coding/_RecordFieldPayloadConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CorePersistence 6 | import Swallow 7 | 8 | public protocol _RecordFieldPayloadConvertible { 9 | func _toRecordFieldPayload() throws -> _RecordFieldPayload 10 | } 11 | 12 | extension Bool: _RecordFieldPayloadConvertible { 13 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 14 | .attribute(value: .primitive(value: .init(self))) 15 | } 16 | } 17 | 18 | extension Character: _RecordFieldPayloadConvertible { 19 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 20 | .attribute(value: .primitive(value: .init(self))) 21 | } 22 | } 23 | 24 | extension Date: _RecordFieldPayloadConvertible { 25 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 26 | .attribute(value: .primitive(value: .init(self))) 27 | } 28 | } 29 | 30 | extension Data: _RecordFieldPayloadConvertible { 31 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 32 | .attribute(value: .primitive(value: .init(self))) 33 | } 34 | } 35 | 36 | extension Decimal: _RecordFieldPayloadConvertible { 37 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 38 | .attribute(value: .primitive(value: .init(self))) 39 | } 40 | } 41 | 42 | extension Double: _RecordFieldPayloadConvertible { 43 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 44 | .attribute(value: .primitive(value: .init(self))) 45 | } 46 | } 47 | 48 | extension Float: _RecordFieldPayloadConvertible { 49 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 50 | .attribute(value: .primitive(value: .init(self))) 51 | } 52 | } 53 | 54 | extension Int: _RecordFieldPayloadConvertible { 55 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 56 | .attribute(value: .primitive(value: .init(self))) 57 | } 58 | } 59 | 60 | extension Int16: _RecordFieldPayloadConvertible { 61 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 62 | .attribute(value: .primitive(value: .init(self))) 63 | } 64 | } 65 | 66 | extension Int32: _RecordFieldPayloadConvertible { 67 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 68 | .attribute(value: .primitive(value: .init(self))) 69 | } 70 | } 71 | 72 | extension Int64: _RecordFieldPayloadConvertible { 73 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 74 | .attribute(value: .primitive(value: .init(self))) 75 | } 76 | } 77 | 78 | extension String: _RecordFieldPayloadConvertible { 79 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 80 | .attribute(value: .primitive(value: .init(self))) 81 | } 82 | } 83 | 84 | extension URL: _RecordFieldPayloadConvertible { 85 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 86 | .attribute(value: .primitive(value: .init(self))) 87 | } 88 | } 89 | 90 | extension UUID: _RecordFieldPayloadConvertible { 91 | public func _toRecordFieldPayload() -> _RecordFieldPayload { 92 | .attribute(value: .primitive(value: .init(self))) 93 | } 94 | } 95 | 96 | extension Array: _RecordFieldPayloadConvertible { 97 | public func _toRecordFieldPayload() throws -> _RecordFieldPayload { 98 | try .attribute(value: .array(value: self.map({ try _TypeSerializingAnyCodable(cast($0, to: Codable.self)) }))) 99 | } 100 | } 101 | 102 | extension Dictionary: _RecordFieldPayloadConvertible { 103 | public func _toRecordFieldPayload() throws -> _RecordFieldPayload { 104 | try .attribute( 105 | value: .dictionary( 106 | value: self 107 | .mapKeys({ try _TypeSerializingAnyCodable(cast($0, to: Codable.self)) }) 108 | .mapValues({ try _TypeSerializingAnyCodable(cast($0, to: Codable.self)) }) 109 | ) 110 | ) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intermodular/Helpers/CoreData/NSManagedObjectContext.ChangesPublisher.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import FoundationX 6 | import Combine 7 | import CoreData 8 | import Swift 9 | 10 | extension NSManagedObjectContext { 11 | public class ChangesPublisher { 12 | private let eventsPublisher = PassthroughSubject<[Event], Never>() 13 | private let managedObjectContext: NSManagedObjectContext 14 | 15 | private weak var persistentStoreCoordinator: NSPersistentStoreCoordinator? 16 | 17 | private var notificationObserver: NSObjectProtocol? 18 | 19 | public init(managedObjectContext: NSManagedObjectContext) { 20 | self.managedObjectContext = managedObjectContext 21 | self.persistentStoreCoordinator = managedObjectContext.persistentStoreCoordinator 22 | 23 | notificationObserver = NotificationCenter.default.addObserver( 24 | forName: NSManagedObjectContext.didChangeObjectsNotification, 25 | object: managedObjectContext, 26 | queue: nil 27 | ) { [weak self] notification in 28 | self?.handleContextObjectDidChangeNotification(notification: notification) 29 | } 30 | } 31 | 32 | private func handleContextObjectDidChangeNotification( 33 | notification: Notification 34 | ) { 35 | guard let incomingContext = notification.object as? NSManagedObjectContext, 36 | let persistentStoreCoordinator = persistentStoreCoordinator, 37 | let incomingPersistentStoreCoordinator = incomingContext.persistentStoreCoordinator, 38 | persistentStoreCoordinator == incomingPersistentStoreCoordinator 39 | else { 40 | return 41 | } 42 | 43 | let insertedObjectsSet = notification.userInfo?[NSInsertedObjectsKey] as? Set ?? Set() 44 | let updatedObjectsSet = notification.userInfo?[NSUpdatedObjectsKey] as? Set ?? Set() 45 | let deletedObjectsSet = notification.userInfo?[NSDeletedObjectsKey] as? Set ?? Set() 46 | let refreshedObjectsSet = notification.userInfo?[NSRefreshedObjectsKey] as? Set ?? Set() 47 | 48 | var events: [Event] = [] 49 | 50 | events += insertedObjectsSet.map({ Event.inserted($0) }) 51 | events += updatedObjectsSet.map({ Event.updated($0) }) 52 | events += deletedObjectsSet.map({ Event.deleted($0) }) 53 | events += refreshedObjectsSet.map({ Event.refreshed($0) }) 54 | 55 | eventsPublisher.send(events) 56 | } 57 | 58 | deinit { 59 | notificationObserver.map(NotificationCenter.default.removeObserver) 60 | } 61 | } 62 | } 63 | 64 | // MARK: - Conformances 65 | 66 | extension NSManagedObjectContext.ChangesPublisher: Publisher { 67 | public typealias Output = [Event] 68 | public typealias Failure = Never 69 | 70 | public func receive(subscriber: S) where S.Input == Output, S.Failure == Never { 71 | eventsPublisher.receive(subscriber: subscriber) 72 | } 73 | } 74 | 75 | // MARK: - Auxiliary 76 | 77 | extension NSManagedObjectContext.ChangesPublisher { 78 | public enum Event { 79 | case updated(NSManagedObject) 80 | case refreshed(NSManagedObject) 81 | case inserted(NSManagedObject) 82 | case deleted(NSManagedObject) 83 | 84 | public func managedObject() -> NSManagedObject { 85 | switch self { 86 | case let .updated(value): 87 | return value 88 | case let .inserted(value): 89 | return value 90 | case let .refreshed(value): 91 | return value 92 | case let .deleted(value): 93 | return value 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Type-erasers/AnyDatabase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Swallow 7 | 8 | /// A type-erased database. 9 | public final class AnyDatabase: Database { 10 | public typealias SchemaAdaptor = AnyDatabaseSchemaAdaptor 11 | public typealias Zone = AnyDatabaseZone 12 | public typealias Record = AnyDatabaseRecord 13 | public typealias TransactionExecutor = AnyDatabaseTransactionExecutor 14 | public typealias RecordSpace = AnyDatabaseRecordSpace 15 | 16 | private let base: any Database 17 | 18 | public var name: String { 19 | base.name.description 20 | } 21 | 22 | public var id: ID { 23 | .init(base: base.id) 24 | } 25 | 26 | public var configuration: Configuration { 27 | .init(base: base.configuration) 28 | } 29 | 30 | public var state: State { 31 | .init(base: base.state) 32 | } 33 | 34 | public var context: Context { 35 | base._opaque_context 36 | } 37 | 38 | public init(erasing database: D) { 39 | if let database = database as? AnyDatabase { 40 | self.base = database.base 41 | } else { 42 | self.base = database 43 | } 44 | } 45 | 46 | public init( 47 | runtime: _SwiftDB_Runtime, 48 | schema: _Schema?, 49 | configuration: Configuration, 50 | state: State? 51 | ) throws { 52 | throw Never.Reason.unsupported 53 | } 54 | 55 | public func querySubscription(for request: ZoneQueryRequest) throws -> AnyDatabaseQuerySubscription { 56 | try base._opaque_querySubscription(for: request) 57 | } 58 | 59 | public func transactionExecutor() throws -> TransactionExecutor { 60 | try .init(erasing: base.transactionExecutor()) 61 | } 62 | 63 | public func fetchAllAvailableZones() -> AnyTask<[AnyDatabaseZone], Error> { 64 | base._opaque_fetchAllAvailableZones() 65 | } 66 | } 67 | 68 | // MARK: - Auxiliary 69 | 70 | extension AnyDatabase { 71 | public struct ID: Codable, Hashable { 72 | let base: AnyCodable 73 | 74 | init(base: Codable) { 75 | self.base = AnyCodable(lazy: base) 76 | } 77 | 78 | public init(from decoder: Decoder) throws { 79 | throw Never.Reason.unsupported 80 | } 81 | } 82 | 83 | public struct Configuration: Codable { 84 | let base: AnyCodable 85 | 86 | init(base: Codable) { 87 | self.base = .init(lazy: base) 88 | } 89 | 90 | public init(from decoder: Decoder) throws { 91 | throw Never.Reason.unsupported 92 | } 93 | } 94 | 95 | public struct State: Codable, Equatable { 96 | let base: AnyCodable? 97 | 98 | init(base: Codable?) { 99 | self.base = base.map({ AnyCodable(lazy: $0) }) 100 | } 101 | 102 | public init(from decoder: Decoder) throws { 103 | throw Never.Reason.unsupported 104 | } 105 | 106 | public init(nilLiteral: ()) { 107 | self.base = nil 108 | } 109 | } 110 | } 111 | 112 | // MARK: - Auxiliary 113 | 114 | fileprivate extension Database { 115 | var _opaque_context: AnyDatabase.Context { 116 | context.eraseToAnyDatabaseContext() 117 | } 118 | 119 | func _opaque_querySubscription( 120 | for request: AnyDatabase.ZoneQueryRequest 121 | ) throws -> AnyDatabaseQuerySubscription { 122 | try .init(erasing: querySubscription(for: request._cast(to: ZoneQueryRequest.self))) 123 | } 124 | 125 | func _opaque_fetchAllAvailableZones() -> AnyTask<[AnyDatabaseZone], Error> { 126 | fetchAllAvailableZones() 127 | .successPublisher 128 | .map({ $0.map(AnyDatabaseZone.init(base:)) }) 129 | .convertToTask() 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Property Wrappers/QueryModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Diagnostics 6 | import FoundationX 7 | import Swallow 8 | import SwiftUIX 9 | import Merge 10 | 11 | /// A property wrapper type that makes fetch requests and retrieves the results from a database. 12 | @propertyWrapper 13 | public struct QueryModels: DynamicProperty { 14 | public typealias WrappedValue = QueryRequest.Output.Results 15 | 16 | @Environment(\.database) var database 17 | 18 | private let queryRequest: QueryRequest 19 | private let transaction: Transaction? 20 | private let animation: Animation? 21 | 22 | @StateObject private var coordinator = RequestOutputCoordinator() 23 | 24 | public var wrappedValue: WrappedValue { 25 | coordinator.querySubscription?.results ?? [] 26 | } 27 | 28 | public var projectedValue: Self { 29 | self 30 | } 31 | 32 | public mutating func update() { 33 | coordinator.queryRequest = queryRequest 34 | coordinator.database = database 35 | } 36 | 37 | public init( 38 | queryRequest: QueryRequest, 39 | animation: Animation? = nil 40 | ) { 41 | self.queryRequest = queryRequest 42 | self.transaction = nil 43 | self.animation = animation 44 | } 45 | 46 | public init( 47 | queryRequest: QueryRequest, 48 | transaction: Transaction 49 | ) { 50 | self.queryRequest = queryRequest 51 | self.transaction = transaction 52 | self.animation = nil 53 | } 54 | 55 | public init( 56 | sortDescriptors: [AnySortDescriptor] = [], 57 | predicate: CocoaPredicate? = nil, 58 | animation: Animation? = nil 59 | ) { 60 | self.init( 61 | queryRequest: .init( 62 | predicate: predicate.map(AnyPredicate.init), 63 | sortDescriptors: sortDescriptors, 64 | fetchLimit: nil, 65 | scope: nil 66 | ), 67 | animation: animation 68 | ) 69 | } 70 | 71 | public init() { 72 | self.init(sortDescriptors: []) 73 | } 74 | } 75 | 76 | extension QueryModels { 77 | public func remove(atOffsets offsets: IndexSet) async throws { 78 | for item in offsets.map({ wrappedValue[$0] }) { 79 | try await database.delete(item) 80 | } 81 | } 82 | 83 | @_disfavoredOverload 84 | @MainActor 85 | public func remove(atOffsets offsets: IndexSet) { 86 | Task { @MainActor in 87 | for item in offsets.map({ wrappedValue[$0] }) { 88 | try await database.delete(item) 89 | } 90 | } 91 | } 92 | } 93 | 94 | // MARK: - Auxiliary 95 | 96 | extension QueryModels { 97 | fileprivate class RequestOutputCoordinator: Logging, ObservableObject, @unchecked Sendable { 98 | var queryRequest: QueryRequest? 99 | 100 | @PublishedObject var querySubscription: QuerySubscription? 101 | 102 | var database: AnyDatabaseContainer.LiveAccess? { 103 | didSet { 104 | guard querySubscription == nil || oldValue !== database else { 105 | return 106 | } 107 | 108 | querySubscription = nil 109 | 110 | guard let queryRequest, let database, database.isInitialized else { 111 | return 112 | } 113 | 114 | Task { @MainActor in 115 | do { 116 | self.querySubscription = try database.querySubscription(for: queryRequest) 117 | } catch { 118 | self.logger.error(error) 119 | } 120 | } 121 | } 122 | } 123 | 124 | init() { 125 | 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Records/Coding/_DatabaseRecordDataDecoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import CorePersistence 6 | import Swallow 7 | 8 | public struct _DatabaseRecordDataDecoder { 9 | let recordSchema: _Schema.Record? 10 | let record: AnyDatabaseRecord 11 | 12 | init( 13 | recordSchema: _Schema.Record?, 14 | record: AnyDatabaseRecord 15 | ) throws { 16 | self.recordSchema = recordSchema 17 | self.record = record 18 | } 19 | } 20 | 21 | extension _DatabaseRecordDataDecoder { 22 | public func containsValue(forKey key: AnyCodingKey) throws -> Bool { 23 | record.containsValue(forKey: key) 24 | } 25 | 26 | public func decodeValue( 27 | _ type: Value.Type, 28 | forKey key: AnyCodingKey 29 | ) throws -> Value { 30 | if let rawRepresentableType = type as? any RawRepresentable.Type, 31 | case .primitive = _Schema.Entity.AttributeType(from: rawRepresentableType) 32 | { 33 | let rawValue = try record._opaque_decode(rawRepresentableType._opaque_RawValue, forKey: key) 34 | 35 | return try cast( 36 | try rawRepresentableType.init(_opaque_rawValue: rawValue), 37 | to: Value.self 38 | ) 39 | } 40 | 41 | return try record.decode(type, forKey: key) 42 | } 43 | 44 | public func decodeValue(forKey key: AnyCodingKey) throws -> Any? { 45 | let property = try _entitySchema().property(named: key.stringValue) 46 | 47 | switch property { 48 | case let property as _Schema.Entity.Attribute: do { 49 | if try !containsValue(forKey: key) { 50 | return nil 51 | } 52 | 53 | let attributeConfiguration = property.attributeConfiguration 54 | 55 | func _decodeValueForType(_ type: T.Type) throws -> Any { 56 | try self.decodeValue(type, forKey: key) 57 | } 58 | 59 | let value = try _openExistential(attributeConfiguration._resolveSwiftType(), do: _decodeValueForType) 60 | 61 | return value 62 | } 63 | case is _Schema.Entity.Relationship: do { 64 | throw _Error.attemptedToDecodeValueFromRelationshipKey(key) 65 | } 66 | default: 67 | throw _Error.unknownPropertyType(property.type, forKey: key) 68 | } 69 | } 70 | 71 | public func decodeRelationship( 72 | forKey key: AnyCodingKey 73 | ) throws -> RelatedDatabaseRecordIdentifiers { 74 | let relationship = try _entitySchema().relationship(named: key.stringValue) 75 | let relationshipType = DatabaseRecordRelationshipType.destinationType(from: relationship) 76 | 77 | return try record.decodeRelationship(ofType: relationshipType, forKey: key) 78 | } 79 | 80 | private func _entitySchema() throws -> _Schema.Entity { 81 | guard let schema = recordSchema as? _Schema.Entity else { 82 | throw _Error.entitySchemaRequired 83 | } 84 | 85 | return schema 86 | } 87 | } 88 | 89 | // MARK: - Auxiliary 90 | 91 | extension _DatabaseRecordDataDecoder { 92 | public enum _Error: _SwiftDB_Error { 93 | case attemptedToDecodeValueFromRelationshipKey(AnyCodingKey) 94 | case entitySchemaRequired 95 | case failedToResolvePrimaryKey 96 | case unknownPropertyType(Any, forKey: CodingKey) 97 | } 98 | } 99 | 100 | extension AnyDatabaseRecord { 101 | fileprivate func _opaque_decode(_ type: Any.Type, forKey key: AnyCodingKey) throws -> Any { 102 | func _decodeForType(_ type: T.Type) throws -> Any { 103 | try self.decode(type, forKey: key) 104 | } 105 | 106 | return try _openExistential(type, do: _decodeForType) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Runtime/_SwiftDB_Runtime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Merge 6 | import Runtime 7 | import Swallow 8 | 9 | public protocol _SwiftDB_Runtime: Sendable { 10 | func metatype(forEntityNamed: String) -> Metatype? 11 | 12 | func convertEntityKeyPathToString(_ keyPath: AnyKeyPath) throws -> String 13 | } 14 | 15 | // MARK: - Conformances 16 | 17 | public final class _SwiftDB_DefaultRuntime: _SwiftDB_Runtime, @unchecked Sendable { 18 | struct TypeCache: Hashable { 19 | var entity: [String: Metatype] = [:] 20 | } 21 | 22 | var typeCache = TypeCache() 23 | var entityCacheMap: [Metatype: EntityCache] = [:] 24 | 25 | public init(schema: _Schema?) throws { 26 | if let schema = schema { 27 | for entity in schema.entities { 28 | _ = try self.entityCache(for: schema.entityType(for: entity.id)) 29 | } 30 | } 31 | } 32 | 33 | public func metatype(forEntityNamed name: String) -> Metatype? { 34 | typeCache.entity[name] 35 | } 36 | 37 | 38 | public final class EntityCache { 39 | let entityType: _opaque_Entity.Type 40 | var fieldNameToKeyPathMap: [String: AnyKeyPath] = [:] 41 | 42 | lazy var prototype: _opaque_Entity = try! entityType.init(_databaseRecordProxy: nil) 43 | 44 | init(entityType: _opaque_Entity.Type) { 45 | self.entityType = entityType 46 | } 47 | 48 | public func _getFieldNameForKeyPath(_ keyPath: AnyKeyPath) throws -> String { 49 | enum KeyPathToFieldNameConversionError: Error { 50 | case valueTypeMismatch(Any.Type, Any.Type) 51 | } 52 | 53 | let valueType = try type(of: cast(keyPath, to: _opaque_KeyPathType.self))._opaque_ValueType 54 | 55 | func _accessKeyPath(_ instance: T) throws { 56 | let keyPath = try cast(keyPath, to: PartialKeyPath.self) 57 | 58 | let _ = instance[keyPath: keyPath] 59 | } 60 | 61 | try _openExistential(prototype, do: _accessKeyPath) 62 | 63 | let field: any _EntityPropertyAccessor = try prototype._runtime_propertyAccessors 64 | .first(where: { $0._runtimeMetadata.didAccessWrappedValueGetter }) 65 | .unwrap() 66 | 67 | guard field._runtimeMetadata.valueType == valueType else { 68 | throw KeyPathToFieldNameConversionError.valueTypeMismatch(field._runtimeMetadata.valueType, valueType) 69 | } 70 | 71 | return try field.name.unwrap() 72 | } 73 | } 74 | 75 | public func entityCache(for entityType: any Entity.Type) -> EntityCache { 76 | if let result = entityCacheMap[.init(entityType)] { 77 | return result 78 | } else { 79 | let result = EntityCache(entityType: entityType) 80 | 81 | entityCacheMap[.init(entityType)] = result 82 | typeCache.entity[String(describing: entityType)] = .init(entityType) 83 | 84 | return result 85 | } 86 | } 87 | 88 | public func convertEntityKeyPathToString(_ keyPath: AnyKeyPath) throws -> String { 89 | let rootType = try type(of: cast(keyPath, to: _opaque_PartialKeyPathType.self))._opaque_RootType 90 | let entityType = try cast(rootType, to: (any Entity.Type).self) 91 | 92 | return try entityCache(for: entityType)._getFieldNameForKeyPath(keyPath) 93 | } 94 | } 95 | 96 | // MARK: - Auxiliary 97 | 98 | extension CodingUserInfoKey { 99 | fileprivate static let _SwiftDB_runtime = CodingUserInfoKey(rawValue: "_SwiftDB_runtime")! 100 | } 101 | 102 | extension Dictionary where Key == CodingUserInfoKey, Value == Any { 103 | public var _SwiftDB_runtime: _SwiftDB_Runtime? { 104 | get { 105 | self[._SwiftDB_runtime] as? _SwiftDB_Runtime 106 | } set { 107 | self[._SwiftDB_runtime] = newValue 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Migration/Schema Mapping/_EntitySchemaMigrationMapping.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Swallow 6 | 7 | public enum _EntitySchemaMigrationMapping: Hashable, TypeDiscriminable { 8 | public enum MappingType: Hashable { 9 | case delete 10 | case insert 11 | case copy 12 | case transform 13 | } 14 | 15 | public typealias Transformer = (CustomEntityTransformerArguments) throws -> Void 16 | 17 | case deleteEntity(sourceEntity: _Schema.Entity.ID) 18 | case insertEntity(destinationEntity: _Schema.Entity.ID) 19 | case copyEntity(sourceEntity: _Schema.Entity.ID, destinationEntity: _Schema.Entity.ID) 20 | case transformEntity( 21 | sourceEntity: _Schema.Entity.ID, 22 | destinationEntity: _Schema.Entity.ID, 23 | transformer: (CustomEntityTransformerArguments) throws -> Void 24 | ) 25 | 26 | public var typeDiscriminator: MappingType { 27 | switch self { 28 | case .deleteEntity: 29 | return .delete 30 | case .insertEntity: 31 | return .insert 32 | case .copyEntity: 33 | return .copy 34 | case .transformEntity: 35 | return .transform 36 | } 37 | } 38 | 39 | public func hash(into hasher: inout Hasher) { 40 | switch self { 41 | case .deleteEntity(let sourceEntity): 42 | hasher.combine(0) 43 | hasher.combine(sourceEntity) 44 | case .insertEntity(let destinationEntity): 45 | hasher.combine(1) 46 | hasher.combine(destinationEntity) 47 | case .copyEntity(let sourceEntity, let destinationEntity): 48 | hasher.combine(2) 49 | hasher.combine(sourceEntity) 50 | hasher.combine(destinationEntity) 51 | case .transformEntity(let sourceEntity, let destinationEntity, _): // FIXME? 52 | hasher.combine(3) 53 | hasher.combine(sourceEntity) 54 | hasher.combine(destinationEntity) 55 | } 56 | } 57 | 58 | public static func == (lhs: Self, rhs: Self) -> Bool { 59 | lhs.hashValue == rhs.hashValue 60 | } 61 | } 62 | 63 | extension _EntitySchemaMigrationMapping { 64 | fileprivate var sourceEntity: _Schema.Entity.ID? { 65 | switch self { 66 | case .deleteEntity(let sourceEntity), .copyEntity(let sourceEntity, _), .transformEntity(let sourceEntity, _, _): 67 | return sourceEntity 68 | case .insertEntity: 69 | return nil 70 | } 71 | } 72 | 73 | fileprivate var destinationEntity: _Schema.Entity.ID? { 74 | switch self { 75 | case .insertEntity(let destinationEntity), .copyEntity(_, let destinationEntity), .transformEntity(_, let destinationEntity, _): 76 | return destinationEntity 77 | case .deleteEntity: 78 | return nil 79 | } 80 | } 81 | } 82 | 83 | // MARK: - Supplementary 84 | 85 | extension _EntitySchemaMigrationMapping { 86 | public static func inferredTransformEntity( 87 | sourceEntity: _Schema.Entity.ID, 88 | destinationEntity: _Schema.Entity.ID 89 | ) -> Self { 90 | .transformEntity( 91 | sourceEntity: sourceEntity, 92 | destinationEntity: destinationEntity, 93 | transformer: inferredTransformer 94 | ) 95 | } 96 | 97 | private static func inferredTransformer(_ args: CustomEntityTransformerArguments) throws -> Void { 98 | let destinationObject = try args.createDestination() 99 | 100 | try destinationObject.enumerateAttributes { attributes in 101 | if let sourceAttribute = attributes.sourceAttribute { 102 | destinationObject[attributes.attribute] = try args.source.decodeFieldPayload(forKey: AnyCodingKey(stringValue: sourceAttribute.name)) 103 | } 104 | } 105 | } 106 | } 107 | 108 | // MARK: - Auxiliary 109 | 110 | public struct CustomEntityTransformerArguments { 111 | let source: _DatabaseRecordProxy 112 | public let createDestination: () throws -> UnsafeRecordMigrationDestination 113 | } 114 | -------------------------------------------------------------------------------- /Sources/SwiftDB/Intramodular/Container/_AssociateDatabaseViewModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Vatsal Manot 3 | // 4 | 5 | import Diagnostics 6 | import Merge 7 | import Swallow 8 | import SwiftUIX 9 | 10 | struct _AssociateDatabaseViewModifier: ViewModifier { 11 | @ObservedObject var container: AnyDatabaseContainer 12 | 13 | @State private var hasAttemptedFirstLoad: Bool = false 14 | 15 | func body(content: Content) -> some View { 16 | Group { 17 | if container.status == .initialized { 18 | content 19 | .environmentObject(container) 20 | .environment(\.database, container.liveAccess) 21 | } else { 22 | if !hasAttemptedFirstLoad { 23 | firstLoadView 24 | } 25 | } 26 | } 27 | } 28 | 29 | private var firstLoadView: some View { 30 | ZeroSizeView().onAppear { 31 | loadIfNeeded() 32 | } 33 | .background { 34 | PerformAction { 35 | loadIfNeeded() 36 | } 37 | } 38 | } 39 | 40 | private func loadIfNeeded() { 41 | guard !hasAttemptedFirstLoad else { 42 | return 43 | } 44 | 45 | Task(priority: .userInitiated) { 46 | try await container.load() 47 | } 48 | 49 | hasAttemptedFirstLoad = true 50 | } 51 | } 52 | 53 | // MARK: - API 54 | 55 | extension View { 56 | /// Attaches a database container to this view. 57 | /// 58 | /// The view is disabled until the database container is initialized. This is intentionally done to prevent invalid access to an uninitialized database container. 59 | /// 60 | /// - Parameters: 61 | /// - container: The database container to attach. 62 | public func database( 63 | _ container: AnyDatabaseContainer 64 | ) -> some View { 65 | modifier(_AssociateDatabaseViewModifier(container: container)) 66 | } 67 | } 68 | 69 | // MARK: - Auxiliary 70 | 71 | extension EnvironmentValues { 72 | fileprivate struct _DatabaseEnvironmentKey: EnvironmentKey { 73 | static let defaultValue = AnyDatabaseContainer.LiveAccess() 74 | } 75 | 76 | /// The database record space associated with this environment. 77 | public fileprivate(set) var database: AnyDatabaseContainer.LiveAccess { 78 | get { 79 | self[_DatabaseEnvironmentKey.self] 80 | } set { 81 | self[_DatabaseEnvironmentKey.self] = newValue 82 | } 83 | } 84 | } 85 | 86 | // MARK: - Diagnostics - 87 | 88 | extension _AssociateDatabaseViewModifier { 89 | struct DiagnosticView: View { 90 | @ObservedObject var container: AnyDatabaseContainer 91 | 92 | var body: some View { 93 | NavigationStack { 94 | _MirrorView(mirror: container.customMirror) 95 | .navigationTitle("Database Summary") 96 | } 97 | } 98 | } 99 | } 100 | 101 | @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) 102 | public struct _MirrorView: View { 103 | public let mirror: Mirror 104 | 105 | public init(mirror: Mirror) { 106 | self.mirror = mirror 107 | } 108 | 109 | public var body: some View { 110 | Form { 111 | Content(mirror: mirror) 112 | } 113 | } 114 | 115 | struct Content: View { 116 | let mirror: Mirror 117 | 118 | var body: some View { 119 | ForEach(Array(mirror.children.enumerated()), id: \.offset) { (offset, labelAndValue) in 120 | let label = labelAndValue.label ?? offset.description 121 | let value = labelAndValue.value 122 | 123 | if Mirror(reflecting: labelAndValue.value).children.count <= 1 { 124 | #if !os(macOS) && !targetEnvironment(macCatalyst) 125 | LabeledContent(label) { 126 | Text(String(describing: value)) 127 | } 128 | #endif 129 | } else { 130 | NavigationLink(label) { 131 | _MirrorView(mirror: Mirror(reflecting: value)) 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | --------------------------------------------------------------------------------