Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 17 additions & 15 deletions Sources/CoreModel/InMemoryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import Foundation
///
/// The type is thread-safe: all access is serialized so the actor-isolated
/// ``InMemoryModelStorage`` and the main-actor ``InMemoryViewContext`` can operate
/// on the same instance concurrently. Under Embedded Swift, where the view context is
/// unavailable, the backing is only ever touched from within its owning actor and the
/// lock is elided.
final class InMemoryStorage {
/// on the same instance concurrently. Under Embedded Swift the lock is elided:
/// with a concurrency runtime the backing is only ever touched from within its
/// owning actor, and without one (e.g. bare-metal ARM, where ``ModelStorage``
/// itself is unavailable) this synchronous store is the storage API, used
/// directly from the single-threaded main loop.
public final class InMemoryStorage {

/// The schema entities are validated against.
let model: Model
public let model: Model

private var objects = [EntityName: [ObjectID: ModelData]]()

Expand All @@ -35,7 +37,7 @@ final class InMemoryStorage {
private let lock = NSLock()
#endif

init(model: Model) {
public init(model: Model) {
self.model = model
}

Expand All @@ -47,50 +49,50 @@ final class InMemoryStorage {
return try body()
}

func fetch(_ entity: EntityName, for id: ObjectID) throws(CoreModelError) -> ModelData? {
public func fetch(_ entity: EntityName, for id: ObjectID) throws(CoreModelError) -> ModelData? {
try withLock { () throws(CoreModelError) in
try validate(entity)
return objects[entity]?[id]
}
}

func fetch(_ fetchRequest: FetchRequest) throws(CoreModelError) -> [ModelData] {
public func fetch(_ fetchRequest: FetchRequest) throws(CoreModelError) -> [ModelData] {
try withLock { () throws(CoreModelError) in
try validate(fetchRequest.entity)
let values = objects[fetchRequest.entity].map { Array($0.values) } ?? []
return fetchRequest.evaluate(values, functions: functions)
}
}

func fetchID(_ fetchRequest: FetchRequest) throws(CoreModelError) -> [ObjectID] {
public func fetchID(_ fetchRequest: FetchRequest) throws(CoreModelError) -> [ObjectID] {
try fetch(fetchRequest).map { $0.id }
}

func count(_ fetchRequest: FetchRequest) throws(CoreModelError) -> UInt {
public func count(_ fetchRequest: FetchRequest) throws(CoreModelError) -> UInt {
try UInt(fetch(fetchRequest).count)
}

func insert(_ value: ModelData) throws(CoreModelError) {
public func insert(_ value: ModelData) throws(CoreModelError) {
try withLock { () throws(CoreModelError) in
try validate(value.entity)
objects[value.entity, default: [:]][value.id] = value
}
}

func insert(_ values: [ModelData]) throws(CoreModelError) {
public func insert(_ values: [ModelData]) throws(CoreModelError) {
for value in values {
try insert(value)
}
}

func delete(_ entity: EntityName, for id: ObjectID) throws(CoreModelError) {
public func delete(_ entity: EntityName, for id: ObjectID) throws(CoreModelError) {
try withLock { () throws(CoreModelError) in
try validate(entity)
objects[entity]?[id] = nil
}
}

func delete(_ entity: EntityName, for ids: [ObjectID]) throws(CoreModelError) {
public func delete(_ entity: EntityName, for ids: [ObjectID]) throws(CoreModelError) {
try withLock { () throws(CoreModelError) in
try validate(entity)
for id in ids {
Expand All @@ -99,7 +101,7 @@ final class InMemoryStorage {
}
}

func register(function: DatabaseFunction) {
public func register(function: DatabaseFunction) {
withLock {
functions[function.name] = function
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/CoreModel/InMemoryStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
// Copyright © 2026 PureSwift. All rights reserved.
//

// - Note: Requires the `_Concurrency` runtime (an `actor`). Embedded targets
// without a concurrency runtime (e.g. bare-metal ARM) use the synchronous
// ``InMemoryStorage`` backing directly instead.
#if canImport(_Concurrency)

/// CoreModel In-Memory Store
///
/// A ``ModelStorage`` backend that keeps all objects in memory,
Expand Down Expand Up @@ -113,3 +118,5 @@ public extension InMemoryModelStorage {
// same API with typed throws.
extension InMemoryModelStorage: ModelStorage {}
#endif

#endif // canImport(_Concurrency)
6 changes: 6 additions & 0 deletions Sources/CoreModel/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
// Copyright © 2015 PureSwift. All rights reserved.
//

// - Note: Requires the `_Concurrency` runtime (`async` requirements). Embedded
// targets whose stdlib ships no `_Concurrency` slice (e.g. bare-metal ARM)
// have no asynchronous storage abstraction — they use a concrete synchronous
// store such as ``InMemoryStorage`` directly.
#if canImport(_Concurrency)
/// CoreModel Store Protocol
public protocol ModelStorage: AnyObject, Sendable {

Expand Down Expand Up @@ -41,6 +46,7 @@ public protocol ModelStorage: AnyObject, Sendable {
/// in memory against fetched values.
func register(function: DatabaseFunction) async throws
}
#endif // canImport(_Concurrency)

#if !hasFeature(Embedded)
// - Note: Unavailable under Embedded Swift (a compiler bug in SILGen crashes on
Expand Down
Loading