Skip to content

Commit c2e0b5f

Browse files
committed
chore: add docc generation step to release.yml
1 parent b357c5a commit c2e0b5f

6 files changed

Lines changed: 164 additions & 29 deletions

File tree

.github/workflows/release.yml

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,42 @@ jobs:
9999
name: ${{ steps.next-version.outputs.NEXT_VERSION }}
100100
tag_name: ${{ steps.next-version.outputs.NEXT_VERSION }}
101101
body: ${{ steps.release-notes.outputs.RELEASE_NOTES }}
102-
target_commitish: ${{ steps.auto-commit-action.outputs.commit_hash }}
102+
target_commitish: ${{ steps.auto-commit-action.outputs.commit_hash }}
103+
104+
docc:
105+
name: build and deploy docc
106+
runs-on: macos-latest
107+
needs: release
108+
if: ${{ needs.release.outputs.has-changes == 'true' }}
109+
timeout-minutes: 15
110+
steps:
111+
- uses: actions/checkout@v6
112+
with:
113+
ref: ${{ needs.release.outputs.commit-hash }}
114+
fetch-depth: 0
115+
- name: Build DocC
116+
id: build
117+
uses: space-code/build-docc@main
118+
with:
119+
schemes: '["Typhoon"]'
120+
version: ${{ needs.release.outputs.next-version }}
121+
- name: Generate Index Page
122+
uses: space-code/generate-index@v1.0.0
123+
with:
124+
version: ${{ needs.release.outputs.next-version }}
125+
project-name: 'Typhoon'
126+
project-description: 'Typhoon is a modern, lightweight Swift framework that provides elegant and robust retry policies for asynchronous operations.'
127+
modules: |
128+
[
129+
{
130+
"name": "Typhoon",
131+
"path": "typhoon",
132+
"description": "Core retry mechanisms and policies for asynchronous operations. Includes retry strategies, backoff algorithms, and cancellation support.",
133+
"badge": "Core Module"
134+
}
135+
]
136+
- name: Deploy
137+
uses: peaceiris/actions-gh-pages@v4
138+
with:
139+
github_token: ${{ secrets.GITHUB_TOKEN }}
140+
publish_dir: ./docs

README.md

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ Typhoon is a modern, lightweight Swift framework that provides elegant and robus
1616

1717
## Features
1818

19-
**Multiple Retry Strategies** - Constant, exponential, and exponential with jitter
19+
**Multiple Retry Strategies** - Constant, exponential, and exponential with jitter
2020
**Async/Await Native** - Built for modern Swift concurrency
21-
🎯 **Type-Safe** - Leverages Swift's type system for compile-time safety
21+
🎯 **Type-Safe** - Leverages Swift's type system for compile-time safety
2222
🔧 **Configurable** - Flexible retry parameters for any use case
23-
📱 **Cross-Platform** - Works on iOS, macOS, tvOS, watchOS, and visionOS
23+
📱 **Cross-Platform** - Works on iOS, macOS, tvOS, watchOS, and visionOS
2424
**Lightweight** - Minimal footprint with zero dependencies
2525
🧪 **Well Tested** - Comprehensive test coverage
2626

@@ -35,8 +35,8 @@ Typhoon is a modern, lightweight Swift framework that provides elegant and robus
3535
- [Exponential Strategy](#exponential-strategy)
3636
- [Exponential with Jitter Strategy](#exponential-with-jitter-strategy)
3737
- [Common Use Cases](#common-use-cases)
38-
- [Error Handling](#error-handling)
3938
- [Communication](#communication)
39+
- [Documentation](#documentation)
4040
- [Contributing](#contributing)
4141
- [Development Setup](#development-setup)
4242
- [Author](#author)
@@ -291,37 +291,17 @@ class PaymentService {
291291
}
292292
```
293293

294-
## Error Handling
295-
296-
Typhoon preserves the original error from your operation after all retry attempts are exhausted:
297-
298-
```swift
299-
import Typhoon
300-
301-
let service = RetryPolicyService(
302-
strategy: .constant(retry: 3, duration: .seconds(1))
303-
)
304-
305-
do {
306-
try await service.retry {
307-
throw NetworkError.serverUnavailable
308-
}
309-
} catch let error as NetworkError {
310-
// Handle specific error type
311-
print("Network error: \(error)")
312-
} catch {
313-
// Handle other errors
314-
print("Unexpected error: \(error)")
315-
}
316-
```
317-
318294
## Communication
319295

320296
- 🐛 **Found a bug?** [Open an issue](https://github.com/space-code/typhoon/issues/new)
321297
- 💡 **Have a feature request?** [Open an issue](https://github.com/space-code/typhoon/issues/new)
322298
-**Questions?** [Start a discussion](https://github.com/space-code/typhoon/discussions)
323299
- 🔒 **Security issue?** Email nv3212@gmail.com
324300

301+
## Documentation
302+
303+
Comprehensive documentation is available: [Typhoon Documentation](https://space-code.github.io/typhoon/typhoon/documentation/typhoon/)
304+
325305
## Contributing
326306

327307
We love contributions! Please feel free to help out with this project. If you see something that could be made better or want a new feature, open up an issue or send a Pull Request.

Sources/Typhoon/Classes/Extensions/DispatchTimeInterval+Double.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@
66
import Foundation
77

88
extension DispatchTimeInterval {
9+
/// Converts a `DispatchTimeInterval` value into seconds represented as `Double`.
10+
///
11+
/// This computed property normalizes all supported `DispatchTimeInterval` cases
12+
/// (`seconds`, `milliseconds`, `microseconds`, `nanoseconds`) into a single
13+
/// unit — **seconds** — which simplifies time calculations and conversions.
14+
///
15+
/// For example:
16+
/// - `.seconds(2)` → `2.0`
17+
/// - `.milliseconds(500)` → `0.5`
18+
/// - `.microseconds(1_000)` → `0.001`
19+
/// - `.nanoseconds(1_000_000_000)` → `1.0`
20+
///
21+
/// - Returns: The interval expressed in seconds as `Double`,
22+
/// or `nil` if the interval represents `.never` or an unknown case.
923
var double: Double? {
1024
switch self {
1125
case let .seconds(value):

Sources/Typhoon/Classes/RetryPolicyService/IRetryPolicyService.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ public extension IRetryPolicyService {
4646
try await retry(strategy: strategy, onFailure: nil, closure)
4747
}
4848

49+
/// Retries a closure with a given strategy.
50+
///
51+
/// - Parameters:
52+
/// - onFailure: An optional closure called on each failure to handle or log errors.
53+
/// - closure: The closure that will be retried based on the specified strategy.
54+
///
55+
/// - Returns: The result of the closure's execution after retrying based on the policy.
4956
func retry<T>(_ closure: @Sendable () async throws -> T, onFailure: (@Sendable (Error) async -> Bool)?) async throws -> T {
5057
try await retry(strategy: nil, onFailure: onFailure, closure)
5158
}

Sources/Typhoon/Classes/RetryPolicyService/RetryPolicyService.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ public final class RetryPolicyService {
2727
// MARK: IRetryPolicyService
2828

2929
extension RetryPolicyService: IRetryPolicyService {
30+
/// Retries a closure with a given strategy.
31+
///
32+
/// - Parameters:
33+
/// - strategy: The strategy defining the behavior of the retry policy.
34+
/// - onFailure: An optional closure called on each failure to handle or log errors.
35+
/// - closure: The closure that will be retried based on the specified strategy.
36+
///
37+
/// - Returns: The result of the closure's execution after retrying based on the policy.
3038
public func retry<T>(
3139
strategy: RetryPolicyStrategy?,
3240
onFailure: (@Sendable (Error) async -> Bool)?,

Sources/Typhoon/Classes/RetrySequence/Iterator/RetryIterator.swift

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,54 @@ import Foundation
77

88
// MARK: - RetryIterator
99

10+
/// An iterator that generates retry delays according to a retry policy strategy.
11+
///
12+
/// `RetryIterator` conforms to `IteratorProtocol` and produces a sequence of delay
13+
/// values (in nanoseconds) that can be used to schedule retry attempts for
14+
/// asynchronous operations such as network requests or background tasks.
15+
///
16+
/// Each call to `next()` returns the delay for the current retry attempt and then
17+
/// advances the internal retry counter. When the maximum number of retries defined
18+
/// by the strategy is reached, the iterator stops producing values.
1019
struct RetryIterator: IteratorProtocol {
1120
// MARK: Properties
1221

22+
/// The current retry attempt index.
23+
///
24+
/// Starts from `0` and is incremented after each successful call to `next()`.
25+
/// This value is used when calculating exponential backoff delays.
1326
private var retries: UInt = 0
1427

28+
/// The retry policy strategy that defines:
29+
/// - The maximum number of retry attempts.
30+
/// - The algorithm used to calculate delays between retries
31+
/// (constant, exponential, or exponential with jitter).
1532
private let strategy: RetryPolicyStrategy
1633

1734
// MARK: Initialization
1835

36+
/// Creates a new `RetryIterator` with the specified retry policy strategy.
37+
///
38+
/// - Parameter strategy: A `RetryPolicyStrategy` describing how retry delays
39+
/// should be calculated and how many retries are allowed.
1940
init(strategy: RetryPolicyStrategy) {
2041
self.strategy = strategy
2142
}
2243

2344
// MARK: IteratorProtocol
2445

46+
/// Returns the delay for the next retry attempt.
47+
///
48+
/// The delay is calculated according to the current retry policy strategy
49+
/// and expressed in **nanoseconds**, making it suitable for use with
50+
/// `DispatchQueue`, `Task.sleep`, or other low-level scheduling APIs.
51+
///
52+
/// After the delay is calculated, the internal retry counter is incremented.
53+
/// When the maximum number of retries is exceeded, this method returns `nil`,
54+
/// signaling the end of the iteration.
55+
///
56+
/// - Returns: The delay in nanoseconds for the current retry attempt,
57+
/// or `nil` if no more retries are allowed.
2558
mutating func next() -> UInt64? {
2659
guard isValid() else { return nil }
2760

@@ -32,10 +65,22 @@ struct RetryIterator: IteratorProtocol {
3265

3366
// MARK: Private
3467

68+
/// Determines whether another retry attempt is allowed.
69+
///
70+
/// This method compares the current retry count with the maximum
71+
/// number of retries defined in the retry strategy.
72+
///
73+
/// - Returns: `true` if another retry attempt is allowed;
74+
/// `false` otherwise.
3575
private func isValid() -> Bool {
3676
retries < strategy.retries
3777
}
3878

79+
/// Calculates the delay for the current retry attempt
80+
/// based on the selected retry strategy.
81+
///
82+
/// - Returns: The computed delay in nanoseconds, or `0`
83+
/// if the duration cannot be converted to seconds.
3984
private func delay() -> UInt64? {
4085
switch strategy {
4186
case let .constant(_, duration):
@@ -61,11 +106,26 @@ struct RetryIterator: IteratorProtocol {
61106

62107
// MARK: - Helper Methods
63108

109+
/// Converts a `DispatchTimeInterval` to nanoseconds.
110+
///
111+
/// - Parameter duration: The time interval to convert.
112+
/// - Returns: The equivalent duration in nanoseconds, or `0`
113+
/// if the interval cannot be represented as seconds.
64114
private func convertToNanoseconds(_ duration: DispatchTimeInterval) -> UInt64? {
65115
guard let seconds = duration.double else { return .zero }
66116
return safeConvertToUInt64(seconds * .nanosec)
67117
}
68118

119+
/// Calculates an exponential backoff delay without jitter.
120+
///
121+
/// The delay is calculated as:
122+
/// `baseDelay * multiplier ^ retries`
123+
///
124+
/// - Parameters:
125+
/// - duration: The base delay value.
126+
/// - multiplier: The exponential growth multiplier.
127+
/// - retries: The current retry attempt index.
128+
/// - Returns: The calculated delay in nanoseconds.
69129
private func calculateExponentialDelay(
70130
duration: DispatchTimeInterval,
71131
multiplier: Double,
@@ -79,6 +139,20 @@ struct RetryIterator: IteratorProtocol {
79139
return safeConvertToUInt64(value)
80140
}
81141

142+
/// Calculates an exponential backoff delay with jitter and an optional maximum interval.
143+
///
144+
/// This method:
145+
/// 1. Calculates the exponential backoff delay.
146+
/// 2. Applies a random jitter to spread retry attempts over time.
147+
/// 3. Caps the result at the provided maximum interval, if any.
148+
///
149+
/// - Parameters:
150+
/// - duration: The base delay value.
151+
/// - multiplier: The exponential growth multiplier.
152+
/// - retries: The current retry attempt index.
153+
/// - jitterFactor: The percentage of randomness applied to the delay.
154+
/// - maxInterval: An optional upper bound for the delay.
155+
/// - Returns: The final delay in nanoseconds.
82156
private func calculateExponentialDelayWithJitter(
83157
duration: DispatchTimeInterval,
84158
multiplier: Double,
@@ -107,6 +181,10 @@ struct RetryIterator: IteratorProtocol {
107181
return safeConvertToUInt64(min(delayWithJitter, maxDelayNanos))
108182
}
109183

184+
/// Calculates the maximum allowed delay in nanoseconds.
185+
///
186+
/// - Parameter maxInterval: An optional maximum delay value.
187+
/// - Returns: The maximum delay in nanoseconds, clamped to `UInt64.max`.
110188
private func calculateMaxDelay(_ maxInterval: DispatchTimeInterval?) -> Double {
111189
guard let maxSeconds = maxInterval?.double else {
112190
return Double(UInt64.max)
@@ -116,6 +194,16 @@ struct RetryIterator: IteratorProtocol {
116194
return min(maxNanos, Double(UInt64.max))
117195
}
118196

197+
/// Applies random jitter to a delay value.
198+
///
199+
/// Jitter helps prevent synchronized retries (the "thundering herd" problem)
200+
/// by randomizing retry timings within a defined range.
201+
///
202+
/// - Parameters:
203+
/// - value: The base delay value in nanoseconds.
204+
/// - factor: The jitter factor defining the randomization range.
205+
/// - maxDelay: The maximum allowed delay.
206+
/// - Returns: A jittered delay value clamped to valid bounds.
119207
private func applyJitter(
120208
to value: Double,
121209
factor: Double,

0 commit comments

Comments
 (0)