From 6c36b4fd2f8df1e70480452e0697e04c38f8d531 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Tue, 23 Dec 2025 05:03:07 -0800 Subject: [PATCH 1/3] Renamed internal load methods to be more consistent --- .../Datastore/Datastore.swift | 31 ++++++++++--------- .../Indexes/IndexRangeExpression.swift | 1 + 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/Sources/CodableDatastore/Datastore/Datastore.swift b/Sources/CodableDatastore/Datastore/Datastore.swift index b32a788..c419baf 100644 --- a/Sources/CodableDatastore/Datastore/Datastore.swift +++ b/Sources/CodableDatastore/Datastore/Datastore.swift @@ -263,7 +263,7 @@ extension Datastore { /// Create any missing indexes and prime the datastore for writing. try await transaction.apply(descriptor: updatedDescriptor, for: key) - let primaryIndex = _load(IndexRange(), order: .ascending, awaitWarmup: false) + let primaryIndex = _load(range: IndexRange(), order: .ascending, awaitWarmup: false) let versionData = try Data(self.version) @@ -505,7 +505,7 @@ extension Datastore { /// - awaitWarmup: Whether the sequence should await warmup or jump right into loading. /// - Returns: An asynchronous sequence containing the instances matching the range of values in that sequence. nonisolated func _load( - _ identifierRange: some IndexRangeExpression & Sendable, + range identifierRange: some IndexRangeExpression & Sendable, order: RangeOrder, awaitWarmup: Bool ) -> some TypedAsyncSequence<(id: IdentifierType, instance: InstanceType)> & Sendable { @@ -519,7 +519,10 @@ extension Datastore { options: [.readOnly] ) { transaction, _ in do { - try await transaction.primaryIndexScan(range: identifierRange.applying(order), datastoreKey: self.key) { versionData, instanceData in + try await transaction.primaryIndexScan( + range: identifierRange.applying(order), + datastoreKey: self.key + ) { versionData, instanceData in let entryVersion = try Version(versionData) let decoder = try await self.decoder(for: entryVersion) let decodedValue = try await decoder(instanceData) @@ -544,7 +547,7 @@ extension Datastore { _ identifierRange: some IndexRangeExpression & Sendable, order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable where IdentifierType: RangedIndexable { - _load(identifierRange, order: order, awaitWarmup: true) + _load(range: identifierRange, order: order, awaitWarmup: true) .map { $0.instance } } @@ -574,24 +577,24 @@ extension Datastore { _ unboundedRange: Swift.UnboundedRange, order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable { - _load(IndexRange(), order: order, awaitWarmup: true) + _load(range: IndexRange.unbounded, order: order, awaitWarmup: true) .map { $0.instance } } /// **Internal:** Load a range of instances from a given index as an async sequence. /// - Parameters: + /// - index: The index to load from. /// - range: The range to load. /// - order: The order to process instances in. - /// - index: The index to load from. /// - Returns: An asynchronous sequence containing the instances matching the range of values in that sequence. @usableFromInline nonisolated func _load< Index: IndexRepresentation, Bound: Indexable >( - _ range: some IndexRangeExpression & Sendable, - order: RangeOrder = .ascending, - from index: KeyPath + index: KeyPath, + range: some IndexRangeExpression & Sendable, + order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable { let declaredIndex = self.indexRepresentations[AnyIndexRepresentation(indexRepresentation: self.format[keyPath: index])] @@ -659,7 +662,7 @@ extension Datastore { order: RangeOrder = .ascending, from index: KeyPath ) -> some TypedAsyncSequence & Sendable { - _load(IndexRange(only: value), order: order, from: index) + _load(index: index, range: IndexRange(only: value), order: order) } /// Load an instance with the matching indexed value, or return nil if one is not found. @@ -676,7 +679,7 @@ extension Datastore { _ value: Value, from index: KeyPath ) async throws -> InstanceType? { - try await _load(IndexRange(only: value), from: index).first(where: { _ in true }) + try await _load(index: index, range: IndexRange(only: value)).first(where: { _ in true }) } /// Load a range of instances from a given index as an async sequence. @@ -697,7 +700,7 @@ extension Datastore { order: RangeOrder = .ascending, from index: KeyPath ) -> some TypedAsyncSequence & Sendable { - _load(range, order: order, from: index) + _load(index: index, range: range, order: order) } /// Load a range of instances from a given index as an async sequence. @@ -719,7 +722,7 @@ extension Datastore { order: RangeOrder = .ascending, from index: KeyPath ) -> some TypedAsyncSequence & Sendable { - _load(range, order: order, from: index) + _load(index: index, range: range, order: order) } /// Load all instances in a datastore in index order as an async sequence. @@ -737,7 +740,7 @@ extension Datastore { order: RangeOrder = .ascending, from index: KeyPath ) -> some TypedAsyncSequence & Sendable { - _load(IndexRange.unbounded, order: order, from: index) + _load(index: index, range: IndexRange.unbounded, order: order) } } diff --git a/Sources/CodableDatastore/Indexes/IndexRangeExpression.swift b/Sources/CodableDatastore/Indexes/IndexRangeExpression.swift index 6b48556..2a82ab9 100644 --- a/Sources/CodableDatastore/Indexes/IndexRangeExpression.swift +++ b/Sources/CodableDatastore/Indexes/IndexRangeExpression.swift @@ -68,6 +68,7 @@ extension IndexRangeExpression { ) } + @usableFromInline static var unbounded: IndexRange { IndexRange() } From e35a9b070b1cd77dbb241cb7f399b4e5f7116a21 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Tue, 23 Dec 2025 05:50:15 -0800 Subject: [PATCH 2/3] Renamed all datastore accessors to enable better autocomplete options --- .../Datastore/Datastore.swift | 178 ++++++++++++------ 1 file changed, 118 insertions(+), 60 deletions(-) diff --git a/Sources/CodableDatastore/Datastore/Datastore.swift b/Sources/CodableDatastore/Datastore/Datastore.swift index c419baf..1751005 100644 --- a/Sources/CodableDatastore/Datastore/Datastore.swift +++ b/Sources/CodableDatastore/Datastore/Datastore.swift @@ -469,10 +469,11 @@ extension Datastore { } } - /// Load an instance with a given identifier, or return nil if one is not found. + /// Load an instance with a given identifier, or return `nil` if one is not found. + /// /// - Parameter identifier: The identifier of the instance to load. - /// - Returns: The instance keyed to the identifier, or nil if none are found. - public func load(_ identifier: IdentifierType) async throws -> InstanceType? { + /// - Returns: The instance keyed to the identifier, or `nil` if none are found. + public func load(id identifier: IdentifierType) async throws -> InstanceType? { try await warmupIfNeeded() return try await persistence._withTransaction( @@ -499,6 +500,7 @@ extension Datastore { } /// **Internal:** Load a range of instances from a datastore based on the identifier range passed in as an async sequence. + /// /// - Parameters: /// - identifierRange: The range to load. /// - order: The order to process instances in. @@ -538,13 +540,13 @@ extension Datastore { /// Load a range of instances from a datastore based on the identifier range passed in as an async sequence. /// - /// The sequence should be consumed a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. /// - Parameters: /// - identifierRange: The range to load. /// - order: The order to process instances in. /// - Returns: An asynchronous sequence containing the instances matching the range of identifiers. public nonisolated func load( - _ identifierRange: some IndexRangeExpression & Sendable, + range identifierRange: some IndexRangeExpression & Sendable, order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable where IdentifierType: RangedIndexable { _load(range: identifierRange, order: order, awaitWarmup: true) @@ -553,28 +555,29 @@ extension Datastore { /// Load a range of instances from a datastore based on the identifier range passed in as an async sequence. /// - /// The sequence should be consumed a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. /// - Parameters: /// - identifierRange: The range to load. /// - order: The order to process instances in. /// - Returns: An asynchronous sequence containing the instances matching the range of identifiers. @_disfavoredOverload + @inlinable public nonisolated func load( - _ identifierRange: IndexRange, + range identifierRange: IndexRange, order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable where IdentifierType: RangedIndexable { - load(identifierRange, order: order) + load(range: identifierRange, order: order) } /// Load all instances in a datastore as an async sequence. /// - /// The sequence should be consumed a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. /// - Parameters: /// - unboundedRange: The range to load. Specify `...` to load every instance. /// - order: The order to process instances in. /// - Returns: An asynchronous sequence containing all the instances. public nonisolated func load( - _ unboundedRange: Swift.UnboundedRange, + range unboundedRange: Swift.UnboundedRange, order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable { _load(range: IndexRange.unbounded, order: order, awaitWarmup: true) @@ -648,36 +651,36 @@ extension Datastore { /// /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified value, but is much more efficient as an index is already maintained for that value. /// - /// The sequence should be consumed a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. /// - Parameters: - /// - value: The value to match against. - /// - order: The order to process instances in. /// - index: The index to load from. + /// - value: The value to match against. + /// - order: The order to process instances in. /// - Returns: An asynchronous sequence containing the instances matching the specified indexed value. public nonisolated func load< Value: DiscreteIndexable, Index: RetrievableIndexRepresentation >( - _ value: Value, - order: RangeOrder = .ascending, - from index: KeyPath + index: KeyPath, + value: Value, + order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable { _load(index: index, range: IndexRange(only: value), order: order) } - /// Load an instance with the matching indexed value, or return nil if one is not found. + /// Load an instance with the matching indexed value, or return `nil` if one is not found. /// /// This requires either a ``DatastoreFormat/OneToOneIndex`` or ``DatastoreFormat/ManyToOneIndex`` to be declared as the index, and a guarantee on the caller's part that at most only a single instance will match the specified value. If multiple instancess match, the one with the identifier that sorts first will be returned. /// - Parameters: - /// - value: The value to match against. /// - index: The index to load from. - /// - Returns: The instance keyed to the specified indexed value, or nil if none are found. + /// - value: The value to match against. + /// - Returns: The instance keyed to the specified indexed value, or `nil` if none are found. public nonisolated func load< Value: DiscreteIndexable, Index: SingleInstanceIndexRepresentation >( - _ value: Value, - from index: KeyPath + index: KeyPath, + value: Value ) async throws -> InstanceType? { try await _load(index: index, range: IndexRange(only: value)).first(where: { _ in true }) } @@ -686,19 +689,19 @@ extension Datastore { /// /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified range, but is much more efficient as an index is already maintained for that range of values. /// - /// The sequence should be consumed a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. /// - Parameters: + /// - index: The index to load from. /// - range: The range to load. /// - order: The order to process instances in. - /// - index: The index to load from. /// - Returns: An asynchronous sequence containing the instances matching the range of values in that sequence. public nonisolated func load< Value: RangedIndexable, Index: RetrievableIndexRepresentation >( - _ range: some IndexRangeExpression & Sendable, - order: RangeOrder = .ascending, - from index: KeyPath + index: KeyPath, + range: some IndexRangeExpression & Sendable, + order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable { _load(index: index, range: range, order: order) } @@ -707,38 +710,38 @@ extension Datastore { /// /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified range, but is much more efficient as an index is already maintained for that range of values. /// - /// The sequence should be consumed a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. /// - Parameters: + /// - index: The index to load from. /// - range: The range to load. /// - order: The order to process instances in. - /// - index: The index to load from. /// - Returns: An asynchronous sequence containing the instances matching the range of values in that sequence. @_disfavoredOverload public nonisolated func load< Value: RangedIndexable, Index: RetrievableIndexRepresentation >( - _ range: IndexRange, - order: RangeOrder = .ascending, - from index: KeyPath + index: KeyPath, + range: IndexRange, + order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable { _load(index: index, range: range, order: order) } /// Load all instances in a datastore in index order as an async sequence. /// - /// The sequence should be consumed a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. /// - /// - Note: If the index is a Mant-to-Any type of index, a smaller or larger number of results may be returned here, as some instances may not be respresented in the index, while others are other-represented and may show up multiple times. + /// - Note: If the index is a Many-to-Any type of index, a smaller or larger number of results may be returned here, as some instances may not be respresented in the index, while others are over-represented and may show up multiple times. /// - Parameters: + /// - index: The index to load from. /// - unboundedRange: The range to load. Specify `...` to load every instance. /// - order: The order to process instances in. - /// - index: The index to load from. /// - Returns: An asynchronous sequence containing all the instances, ordered by the specified index. public nonisolated func load>( - _ unboundedRange: Swift.UnboundedRange, - order: RangeOrder = .ascending, - from index: KeyPath + index: KeyPath, + range unboundedRange: Swift.UnboundedRange, + order: RangeOrder = .ascending ) -> some TypedAsyncSequence & Sendable { _load(index: index, range: IndexRange.unbounded, order: order) } @@ -747,11 +750,19 @@ extension Datastore { // MARK: - Observation extension Datastore { - public func observe(_ identifier: IdentifierType) async throws -> some TypedAsyncSequence> & Sendable { - try await self.observe() - .filter { $0.id == identifier } + /// Observe changes made to an instance with the given identifier. + /// + /// - Parameter identifier: The identifier of the instance to observe. + /// - Returns: An unbounded asynchronous sequence reporting changes to the observed instance. + public func observe( + id identifier: IdentifierType + ) async throws -> some TypedAsyncSequence> & Sendable { + try await observe().filter { $0.id == identifier } } + /// Observe all changes made to a datastore. + /// + /// - Returns: An unbounded asynchronous sequence reporting changes to the datastore. public func observe() async throws -> some TypedAsyncSequence> & Sendable { try await warmupIfNeeded() @@ -786,7 +797,10 @@ extension Datastore where AccessMode == ReadWrite { /// - instance: The instance to persist. /// - identifier: The unique identifier to use to reference the item being persisted. @discardableResult - public func persist(_ instance: InstanceType, to identifier: IdentifierType) async throws -> InstanceType? { + public func persist( + _ instance: InstanceType, + to identifier: IdentifierType + ) async throws -> InstanceType? { try await warmupIfNeeded() let updatedDescriptor = try self.generateUpdatedDescriptor() @@ -964,19 +978,36 @@ extension Datastore where AccessMode == ReadWrite { /// - instance: The instance to persist. /// - keypath: The keypath the identifier is located at. @discardableResult - public func persist(_ instance: InstanceType, id keypath: KeyPath) async throws -> InstanceType? { + public func persist( + _ instance: InstanceType, + id keypath: KeyPath + ) async throws -> InstanceType? { try await persist(instance, to: instance[keyPath: keypath]) } + /// Delete the instance with the given identifier from the datastore. + /// + /// - Throws:Throws ``DatastoreInterfaceError/instanceNotFound`` if the instance does not exist. + /// - Parameter identifier: The identifier of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore. + @inlinable @discardableResult - public func delete(_ identifier: IdentifierType) async throws -> InstanceType { - guard let deletedInstance = try await deleteIfPresent(identifier) + public func delete( + id identifier: IdentifierType + ) async throws -> InstanceType { + guard let deletedInstance = try await deleteIfPresent(id: identifier) else { throw DatastoreInterfaceError.instanceNotFound } return deletedInstance } + /// Delete the instance with the given identifier from the datastore whether it exists or not. + /// + /// - Parameter identifier: The identifier of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore, or `nil` if none are found. @discardableResult - public func deleteIfPresent(_ identifier: IdentifierType) async throws -> InstanceType? { + public func deleteIfPresent( + id identifier: IdentifierType + ) async throws -> InstanceType? { try await warmupIfNeeded() return try await persistence._withTransaction( @@ -1085,32 +1116,59 @@ extension Datastore where InstanceType: Identifiable, IdentifierType == Instance /// /// If an instance does not already exist for the specified identifier, it will be created. If an instance already exists, it will be updated. /// - Parameter instance: The instance to persist. - @_disfavoredOverload + @inlinable @discardableResult - public func persist(_ instance: InstanceType) async throws -> InstanceType? where AccessMode == ReadWrite { - try await self.persist(instance, to: instance.id) + public func persist( + _ instance: InstanceType + ) async throws -> InstanceType? where AccessMode == ReadWrite { + try await persist(instance, to: instance.id) } - @_disfavoredOverload + /// Delete the instance with the same identifier from the datastore. + /// + /// - Throws:Throws ``DatastoreInterfaceError/instanceNotFound`` if the instance does not exist. + /// - Parameter instance: A copy of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore. + @inlinable @discardableResult - public func delete(_ instance: InstanceType) async throws -> InstanceType where AccessMode == ReadWrite { - try await self.delete(instance.id) + public func delete( + instance: InstanceType + ) async throws -> InstanceType where AccessMode == ReadWrite { + try await delete(id: instance.id) } - @_disfavoredOverload + /// Delete the instance with the same identifier from the datastore whether it exists or not. + /// + /// - Parameter instance: A copy of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore, or `nil` if none are found. + @inlinable @discardableResult - public func deleteIfPresent(_ instance: InstanceType) async throws -> InstanceType? where AccessMode == ReadWrite { - try await self.deleteIfPresent(instance.id) + public func deleteIfPresent( + instance: InstanceType + ) async throws -> InstanceType? where AccessMode == ReadWrite { + try await deleteIfPresent(id: instance.id) } - @_disfavoredOverload - public func load(_ instance: InstanceType) async throws -> InstanceType? { - try await self.load(instance.id) + /// Reload an instance with a given identifier and return it, or return `nil` if one is not found. + /// + /// - Parameter instance: A copy of the instance to load. + /// - Returns: The instance keyed to the identifier, or `nil` if none are found. + @inlinable + public func load( + instance: InstanceType + ) async throws -> InstanceType? { + try await load(id: instance.id) } - @_disfavoredOverload - public func observe(_ instance: InstanceType) async throws -> some TypedAsyncSequence> & Sendable { - try await observe(instance.id) + /// Observe changes made to an instance with a given identifier. + /// + /// - Parameter identifier: A copy of the instance to observe. + /// - Returns: An unbounded asynchronous sequence reporting changes to the observed instance. + @inlinable + public func observe( + instance: InstanceType + ) async throws -> some TypedAsyncSequence> & Sendable { + try await observe(id: instance.id) } } From 1cedc6c2c8644bddb33635e6c816a3e1664a9711 Mon Sep 17 00:00:00 2001 From: Dimitri Bouniol Date: Wed, 24 Dec 2025 03:58:15 -0800 Subject: [PATCH 3/3] Re-added elided forms for readability and compatibility with existing code --- .../Datastore/Datastore.swift | 260 ++++++++++++++++++ 1 file changed, 260 insertions(+) diff --git a/Sources/CodableDatastore/Datastore/Datastore.swift b/Sources/CodableDatastore/Datastore/Datastore.swift index 1751005..72219dc 100644 --- a/Sources/CodableDatastore/Datastore/Datastore.swift +++ b/Sources/CodableDatastore/Datastore/Datastore.swift @@ -499,6 +499,16 @@ extension Datastore { } } + /// **[Elided Form]** Load an instance with a given identifier, or return `nil` if one is not found. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(id:)`` instead for better completion support, type inference, and indentation. + /// - Parameter identifier: The identifier of the instance to load. + /// - Returns: The instance keyed to the identifier, or `nil` if none are found. + @inlinable + public func load(_ identifier: IdentifierType) async throws -> InstanceType? { + try await load(id: identifier) + } + /// **Internal:** Load a range of instances from a datastore based on the identifier range passed in as an async sequence. /// /// - Parameters: @@ -553,6 +563,22 @@ extension Datastore { .map { $0.instance } } + /// **[Elided Form]** Load a range of instances from a datastore based on the identifier range passed in as an async sequence. + /// + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(range:order:)-(IndexRangeExpression&Sendable,_)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - identifierRange: The range to load. + /// - order: The order to process instances in. + /// - Returns: An asynchronous sequence containing the instances matching the range of identifiers. + @inlinable + public nonisolated func load( + _ identifierRange: some IndexRangeExpression & Sendable, + order: RangeOrder = .ascending + ) -> some TypedAsyncSequence & Sendable where IdentifierType: RangedIndexable { + load(range: identifierRange, order: order) + } + /// Load a range of instances from a datastore based on the identifier range passed in as an async sequence. /// /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. @@ -569,6 +595,23 @@ extension Datastore { load(range: identifierRange, order: order) } + /// **[Elided Form]** Load a range of instances from a datastore based on the identifier range passed in as an async sequence. + /// + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(range:order:)-(IndexRange,_)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - identifierRange: The range to load. + /// - order: The order to process instances in. + /// - Returns: An asynchronous sequence containing the instances matching the range of identifiers. + @_disfavoredOverload + @inlinable + public nonisolated func load( + _ identifierRange: IndexRange, + order: RangeOrder = .ascending + ) -> some TypedAsyncSequence & Sendable where IdentifierType: RangedIndexable { + load(range: identifierRange, order: order) + } + /// Load all instances in a datastore as an async sequence. /// /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. @@ -584,6 +627,22 @@ extension Datastore { .map { $0.instance } } + /// **[Elided Form]** Load all instances in a datastore as an async sequence. + /// + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(range:order:)-(UnboundedRange,_)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - unboundedRange: The range to load. Specify `...` to load every instance. + /// - order: The order to process instances in. + /// - Returns: An asynchronous sequence containing all the instances. + @inlinable + public nonisolated func load( + _ unboundedRange: Swift.UnboundedRange, + order: RangeOrder = .ascending + ) -> some TypedAsyncSequence & Sendable { + load(range: ..., order: order) + } + /// **Internal:** Load a range of instances from a given index as an async sequence. /// - Parameters: /// - index: The index to load from. @@ -668,6 +727,28 @@ extension Datastore { _load(index: index, range: IndexRange(only: value), order: order) } + /// **[Elided Form]** Load all instances with the matching indexed value as an async sequence. + /// + /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified value, but is much more efficient as an index is already maintained for that value. + /// + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(index:value:order:)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - value: The value to match against. + /// - order: The order to process instances in. + /// - index: The index to load from. + /// - Returns: An asynchronous sequence containing the instances matching the specified indexed value. + public nonisolated func load< + Value: DiscreteIndexable, + Index: RetrievableIndexRepresentation + >( + _ value: Value, + order: RangeOrder = .ascending, + from index: KeyPath + ) -> some TypedAsyncSequence & Sendable { + load(index: index, value: value, order: order) + } + /// Load an instance with the matching indexed value, or return `nil` if one is not found. /// /// This requires either a ``DatastoreFormat/OneToOneIndex`` or ``DatastoreFormat/ManyToOneIndex`` to be declared as the index, and a guarantee on the caller's part that at most only a single instance will match the specified value. If multiple instancess match, the one with the identifier that sorts first will be returned. @@ -685,6 +766,25 @@ extension Datastore { try await _load(index: index, range: IndexRange(only: value)).first(where: { _ in true }) } + /// **[Elided Form]** Load an instance with the matching indexed value, or return `nil` if one is not found. + /// + /// This requires either a ``DatastoreFormat/OneToOneIndex`` or ``DatastoreFormat/ManyToOneIndex`` to be declared as the index, and a guarantee on the caller's part that at most only a single instance will match the specified value. If multiple instancess match, the one with the identifier that sorts first will be returned. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(index:value:)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - value: The value to match against. + /// - index: The index to load from. + /// - Returns: The instance keyed to the specified indexed value, or `nil` if none are found. + @inlinable + public nonisolated func load< + Value: DiscreteIndexable, + Index: SingleInstanceIndexRepresentation + >( + _ value: Value, + from index: KeyPath + ) async throws -> InstanceType? { + try await load(index: index, value: value) + } + /// Load a range of instances from a given index as an async sequence. /// /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified range, but is much more efficient as an index is already maintained for that range of values. @@ -706,6 +806,29 @@ extension Datastore { _load(index: index, range: range, order: order) } + /// **[Elided Form]** Load a range of instances from a given index as an async sequence. + /// + /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified range, but is much more efficient as an index is already maintained for that range of values. + /// + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(index:range:order:)-(_,IndexRangeExpression&Sendable,_)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - range: The range to load. + /// - order: The order to process instances in. + /// - index: The index to load from. + /// - Returns: An asynchronous sequence containing the instances matching the range of values in that sequence. + @inlinable + public nonisolated func load< + Value: RangedIndexable, + Index: RetrievableIndexRepresentation + >( + _ range: some IndexRangeExpression & Sendable, + order: RangeOrder = .ascending, + from index: KeyPath + ) -> some TypedAsyncSequence & Sendable { + load(index: index, range: range, order: order) + } + /// Load a range of instances from a given index as an async sequence. /// /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified range, but is much more efficient as an index is already maintained for that range of values. @@ -728,6 +851,30 @@ extension Datastore { _load(index: index, range: range, order: order) } + /// **[Elided Form]** Load a range of instances from a given index as an async sequence. + /// + /// This is conceptually similar to loading all instances and filtering only those who's indexed key path matches the specified range, but is much more efficient as an index is already maintained for that range of values. + /// + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(index:range:order:)-(_,IndexRange,_)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - range: The range to load. + /// - order: The order to process instances in. + /// - index: The index to load from. + /// - Returns: An asynchronous sequence containing the instances matching the range of values in that sequence. + @_disfavoredOverload + @inlinable + public nonisolated func load< + Value: RangedIndexable, + Index: RetrievableIndexRepresentation + >( + _ range: IndexRange, + order: RangeOrder = .ascending, + from index: KeyPath + ) -> some TypedAsyncSequence & Sendable { + load(index: index, range: range, order: order) + } + /// Load all instances in a datastore in index order as an async sequence. /// /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. @@ -745,6 +892,25 @@ extension Datastore { ) -> some TypedAsyncSequence & Sendable { _load(index: index, range: IndexRange.unbounded, order: order) } + + /// **[Elided Form]** Load all instances in a datastore in index order as an async sequence. + /// + /// - Important: The sequence should be consumed at most a single time, ideally within the same transaction it was created in as it holds a reference to that transaction and thus snapshot of the datastore for data consistency. + /// - Note: If the index is a Many-to-Any type of index, a smaller or larger number of results may be returned here, as some instances may not be respresented in the index, while others are over-represented and may show up multiple times. + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(index:range:order:)-(_,UnboundedRange,_)`` instead for better completion support, type inference, and indentation. + /// - Parameters: + /// - unboundedRange: The range to load. Specify `...` to load every instance. + /// - order: The order to process instances in. + /// - index: The index to load from. + /// - Returns: An asynchronous sequence containing all the instances, ordered by the specified index. + @inlinable + public nonisolated func load>( + _ unboundedRange: Swift.UnboundedRange, + order: RangeOrder = .ascending, + from index: KeyPath + ) -> some TypedAsyncSequence & Sendable { + load(index: index, range: ..., order: order) + } } // MARK: - Observation @@ -760,6 +926,18 @@ extension Datastore { try await observe().filter { $0.id == identifier } } + /// **[Elided Form]** Observe changes made to an instance with the given identifier. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``observe(id:)`` instead for better completion support, type inference, and indentation. + /// - Parameter identifier: The identifier of the instance to observe. + /// - Returns: An unbounded asynchronous sequence reporting changes to the observed instance. + @inlinable + public func observe( + _ identifier: IdentifierType + ) async throws -> some TypedAsyncSequence> & Sendable { + try await observe(id: identifier) + } + /// Observe all changes made to a datastore. /// /// - Returns: An unbounded asynchronous sequence reporting changes to the datastore. @@ -1000,6 +1178,20 @@ extension Datastore where AccessMode == ReadWrite { return deletedInstance } + /// **[Elided Form]** Delete the instance with the given identifier from the datastore. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``observe(id:)`` instead for better completion support, type inference, and indentation. + /// - Throws:Throws ``DatastoreInterfaceError/instanceNotFound`` if the instance does not exist. + /// - Parameter identifier: The identifier of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore. + @inlinable + @discardableResult + public func delete( + _ identifier: IdentifierType + ) async throws -> InstanceType { + try await delete(id: identifier) + } + /// Delete the instance with the given identifier from the datastore whether it exists or not. /// /// - Parameter identifier: The identifier of the instance to delete. @@ -1095,6 +1287,19 @@ extension Datastore where AccessMode == ReadWrite { } } + /// **[Elided Form]** Delete the instance with the given identifier from the datastore whether it exists or not. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``deleteIfPresent(id:)`` instead for better completion support, type inference, and indentation. + /// - Parameter identifier: The identifier of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore, or `nil` if none are found. + @inlinable + @discardableResult + public func deleteIfPresent( + _ identifier: IdentifierType + ) async throws -> InstanceType? { + try await deleteIfPresent(id: identifier) + } + /// A read-only view into the data store. // TODO: Make a proper copy here public var readOnly: Datastore { @@ -1137,6 +1342,21 @@ extension Datastore where InstanceType: Identifiable, IdentifierType == Instance try await delete(id: instance.id) } + /// **[Elided Form]** Delete the instance with the same identifier from the datastore. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``delete(instance:)`` instead for better completion support, type inference, and indentation. + /// - Throws:Throws ``DatastoreInterfaceError/instanceNotFound`` if the instance does not exist. + /// - Parameter instance: A copy of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore. + @_disfavoredOverload + @inlinable + @discardableResult + public func delete( + _ instance: InstanceType + ) async throws -> InstanceType where AccessMode == ReadWrite { + try await delete(instance: instance) + } + /// Delete the instance with the same identifier from the datastore whether it exists or not. /// /// - Parameter instance: A copy of the instance to delete. @@ -1149,6 +1369,20 @@ extension Datastore where InstanceType: Identifiable, IdentifierType == Instance try await deleteIfPresent(id: instance.id) } + /// **[Elided Form]** Delete the instance with the same identifier from the datastore whether it exists or not. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``deleteIfPresent(instance:)`` instead for better completion support, type inference, and indentation. + /// - Parameter instance: A copy of the instance to delete. + /// - Returns: A copy of the instance that was deleted as it existed in the datastore, or `nil` if none are found. + @_disfavoredOverload + @inlinable + @discardableResult + public func deleteIfPresent( + _ instance: InstanceType + ) async throws -> InstanceType? where AccessMode == ReadWrite { + try await deleteIfPresent(instance: instance) + } + /// Reload an instance with a given identifier and return it, or return `nil` if one is not found. /// /// - Parameter instance: A copy of the instance to load. @@ -1160,6 +1394,19 @@ extension Datastore where InstanceType: Identifiable, IdentifierType == Instance try await load(id: instance.id) } + /// **[Elided Form]** Reload an instance with a given identifier and return it, or return `nil` if one is not found. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``load(instance:)`` instead for better completion support, type inference, and indentation. + /// - Parameter instance: A copy of the instance to load. + /// - Returns: The instance keyed to the identifier, or `nil` if none are found. + @_disfavoredOverload + @inlinable + public func load( + _ instance: InstanceType + ) async throws -> InstanceType? { + try await load(instance: instance) + } + /// Observe changes made to an instance with a given identifier. /// /// - Parameter identifier: A copy of the instance to observe. @@ -1170,6 +1417,19 @@ extension Datastore where InstanceType: Identifiable, IdentifierType == Instance ) async throws -> some TypedAsyncSequence> & Sendable { try await observe(id: instance.id) } + + /// **[Elided Form]** Observe changes made to an instance with a given identifier. + /// + /// - SeeAlso: This is form that elides the first argument name instead for better completion support, type inference, and indentation. You may however prefer to use ``observe(instance:)`` instead for better completion support, type inference, and indentation. + /// - Parameter identifier: A copy of the instance to observe. + /// - Returns: An unbounded asynchronous sequence reporting changes to the observed instance. + @_disfavoredOverload + @inlinable + public func observe( + _ instance: InstanceType + ) async throws -> some TypedAsyncSequence> & Sendable { + try await observe(instance: instance) + } } // MARK: - JSON and Plist Stores