diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..51fb131 --- /dev/null +++ b/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version: 6.0 + +import PackageDescription + +let package = Package( + name: "general-types", + platforms: [ + .iOS(.v13), + .macOS(.v10_15), + .tvOS(.v13), + .watchOS(.v6), + .visionOS(.v1) + ], + products: [ + .library(name: "AsyncStreamTypes", targets: ["AsyncStreamTypes"]), + .library(name: "DebuggingTypes", targets: ["DebuggingTypes"]) + ], + dependencies: [ + ], + targets: [ + .target(name: "AsyncStreamTypes"), + .target(name: "DebuggingTypes"), + .testTarget( + name: "AsyncStreamTypesTests", + dependencies: [ + "AsyncStreamTypes", + ] + ), + ] +) diff --git a/README.md b/README.md new file mode 100644 index 0000000..aebaa12 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# General Types + +> [!WARNING] +> +> Please be aware that this package is experimental, integration in a production code should be carefully considered. + +## Overview + +Shared types used in different Swift packages. + +### AsyncStream additions + +`Stream` and `ThrowingStream`are simple typealiases. + +```swift +/// A stream with its continuation. +public typealias Stream = ( + stream: AsyncStream, + continuation: AsyncStream.Continuation +) + +/// A throwing stream with its continuation. +public typealias ThrowingStream = ( + stream: AsyncThrowingStream, + continuation: AsyncThrowingStream.Continuation +) +``` + +For `AsyncStream` and `AsyncThrowingStream` where `Element` is `Sendable` an additional `yield(finalValue: Element)` method has been added to combine two operations in one: Yielding a final value and finishing the stream. + +Small gain but it makes the intent more explicit and help avoid situations where we intend to send a final value and close the stream but we forget to finish the stream leaving the subscriber hanging. + +```swift +let intStream = AsyncStream.makeStream() +// Enqueues values +intStream.continuation.yield(0) +intStream.continuation.yield(1) +intStream.continuation.yield(2) +intStream.continuation.yield(3) + +// Yields the final value and ends the stream. +intStream.continuation.yield(finalValue: 4) +/* +Equivalent of: +intStream.continuation.yield(4) +intStream.continuation.finish() +*/ +``` diff --git a/Sources/AsyncStreamTypes/Stream.swift b/Sources/AsyncStreamTypes/Stream.swift new file mode 100644 index 0000000..c0ce106 --- /dev/null +++ b/Sources/AsyncStreamTypes/Stream.swift @@ -0,0 +1,39 @@ +/// A stream with its continuation. +public typealias Stream = ( + stream: AsyncStream, + continuation: AsyncStream.Continuation +) + +/// A throwing stream with its continuation. +public typealias ThrowingStream = ( + stream: AsyncThrowingStream, + continuation: AsyncThrowingStream.Continuation +) + +extension AsyncStream.Continuation where Element: Sendable { + /// Yields a final value before finishing. + /// + /// Encapuslates two operations: + /// ```swift + ///contiunation.yield(value) + ///contiunation.finish() + /// ``` + public func yield(finalValue: Element) { + self.yield(finalValue) + self.finish() + } +} + +extension AsyncThrowingStream.Continuation where Element: Sendable { + /// Yields a final value before finishing. + /// + /// Encapuslates two operations: + /// ```swift + ///contiunation.yield(value) + ///contiunation.finish() + /// ``` + public func yield(finalValue: Element) { + self.yield(finalValue) + self.finish() + } +} diff --git a/Sources/DebuggingTypes/Debugging.swift b/Sources/DebuggingTypes/Debugging.swift new file mode 100644 index 0000000..0680350 --- /dev/null +++ b/Sources/DebuggingTypes/Debugging.swift @@ -0,0 +1,17 @@ +/// A type used to throw an error on purpose when debuging and testing for throwring logic. +public struct DebugThrow: Error { + public let line: Int + public let file: String + public let comment: String + + public init( + line: Int = #line, + file: String = #file, + comment: String = "" + ) { + self.line = line + self.file = file + self.comment = comment + } +} + diff --git a/Tests/AsyncStreamTypesTests/YieldFinalValueTests.swift b/Tests/AsyncStreamTypesTests/YieldFinalValueTests.swift new file mode 100644 index 0000000..3791279 --- /dev/null +++ b/Tests/AsyncStreamTypesTests/YieldFinalValueTests.swift @@ -0,0 +1,23 @@ +import Testing +import AsyncStreamTypes + +@Test func YieldFinalValue() async throws { + let intStream = AsyncStream.makeStream() + + // Enqueues values + intStream.continuation.yield(0) + intStream.continuation.yield(1) + intStream.continuation.yield(2) + intStream.continuation.yield(3) + + // Yields the final value + intStream.continuation.yield(finalValue: 4) + + var iterator = intStream.stream.makeAsyncIterator() + #expect(await iterator.next() == 0) + #expect(await iterator.next() == 1) + #expect(await iterator.next() == 2) + #expect(await iterator.next() == 3) + #expect(await iterator.next() == 4) + #expect(await iterator.next() == nil) +}