diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4141c79..8f5cd90 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -99,4 +99,42 @@ jobs:
name: ${{ steps.next-version.outputs.NEXT_VERSION }}
tag_name: ${{ steps.next-version.outputs.NEXT_VERSION }}
body: ${{ steps.release-notes.outputs.RELEASE_NOTES }}
- target_commitish: ${{ steps.auto-commit-action.outputs.commit_hash }}
\ No newline at end of file
+ target_commitish: ${{ steps.auto-commit-action.outputs.commit_hash }}
+
+ docc:
+ name: build and deploy docc
+ runs-on: macos-latest
+ needs: release
+ if: ${{ needs.release.outputs.has-changes == 'true' }}
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ ref: ${{ needs.release.outputs.commit-hash }}
+ fetch-depth: 0
+ - name: Build DocC
+ id: build
+ uses: space-code/build-docc@main
+ with:
+ schemes: '["Typhoon"]'
+ version: ${{ needs.release.outputs.next-version }}
+ - name: Generate Index Page
+ uses: space-code/generate-index@v1.0.0
+ with:
+ version: ${{ needs.release.outputs.next-version }}
+ project-name: 'Typhoon'
+ project-description: 'Typhoon is a modern, lightweight Swift framework that provides elegant and robust retry policies for asynchronous operations.'
+ modules: |
+ [
+ {
+ "name": "Typhoon",
+ "path": "typhoon",
+ "description": "Core retry mechanisms and policies for asynchronous operations. Includes retry strategies, backoff algorithms, and cancellation support.",
+ "badge": "Core Module"
+ }
+ ]
+ - name: Deploy
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./docs
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 56c1661..e940fc4 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,73 +2,132 @@
## Our Pledge
-In the interest of fostering an open and welcoming environment, we as
-contributors and maintainers pledge to making participation in our project and
-our community a harassment-free experience for everyone, regardless of age, body
-size, disability, ethnicity, gender identity and expression, level of experience,
-nationality, personal appearance, race, religion, or sexual identity and
-orientation.
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, caste, color, religion, or sexual
+identity and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
## Our Standards
-Examples of behavior that contributes to creating a positive environment
-include:
+Examples of behavior that contributes to a positive environment for our
+community include:
-* Using welcoming and inclusive language
-* Being respectful of differing viewpoints and experiences
-* Gracefully accepting constructive criticism
-* Focusing on what is best for the community
-* Showing empathy towards other community members
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the overall
+ community
-Examples of unacceptable behavior by participants include:
+Examples of unacceptable behavior include:
-* The use of sexualized language or imagery and unwelcome sexual attention or
-advances
-* Trolling, insulting/derogatory comments, and personal or political attacks
+* The use of sexualized language or imagery, and sexual attention or advances of
+ any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
-* Publishing others' private information, such as a physical or electronic
- address, without explicit permission
+* Publishing others' private information, such as a physical or email address,
+ without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
-## Our Responsibilities
+## Enforcement Responsibilities
-Project maintainers are responsible for clarifying the standards of acceptable
-behavior and are expected to take appropriate and fair corrective action in
-response to any instances of unacceptable behavior.
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
-Project maintainers have the right and responsibility to remove, edit, or
-reject comments, commits, code, wiki edits, issues, and other contributions
-that are not aligned to this Code of Conduct, or to ban temporarily or
-permanently any contributor for other behaviors that they deem inappropriate,
-threatening, offensive, or harmful.
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
## Scope
-This Code of Conduct applies both within project spaces and in public spaces
-when an individual is representing the project or its community. Examples of
-representing a project or community include using an official project e-mail
-address, posting via an official social media account, or acting as an appointed
-representative at an online or offline event. Representation of a project may be
-further defined and clarified by project maintainers.
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
-reported by contacting one of the project maintainers https://github.com/orgs/space-code/people. All
-complaints will be reviewed and investigated and will result in a response that
-is deemed necessary and appropriate to the circumstances. The project team is
-obligated to maintain confidentiality with regard to the reporter of an incident.
-Further details of specific enforcement policies may be posted separately.
+reported to the community leaders responsible for enforcement at
+nv3212@gmail.com.
+
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
-Project maintainers who do not follow or enforce the Code of Conduct in good
-faith may face temporary or permanent repercussions as determined by other
-members of the project's leadership.
+**Community Impact**: A violation through a single incident or series of
+actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or permanent
+ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within the
+community.
## Attribution
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
-available at [http://contributor-covenant.org/version/1/4][version]
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.1, available at
+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
+
+Community Impact Guidelines were inspired by
+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
+
+For answers to common questions about this code of conduct, see the FAQ at
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
+[https://www.contributor-covenant.org/translations][translations].
-[homepage]: http://contributor-covenant.org
-[version]: http://contributor-covenant.org/version/1/4/
\ No newline at end of file
+[homepage]: https://www.contributor-covenant.org
+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
+[Mozilla CoC]: https://github.com/mozilla/diversity
+[FAQ]: https://www.contributor-covenant.org/faq
+[translations]: https://www.contributor-covenant.org/translations
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 07f9cbd..43b90f4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,61 +1,518 @@
-# Contributing Guidelines
+# Contributing to Typhoon
-This document contains information and guidelines about contributing to this project.
-Please read it before you start participating.
+First off, thank you for considering contributing to Typhoon! It's people like you that make Typhoon such a great tool.
-**Topics**
+## Table of Contents
-* [Reporting Issues](#reporting-issues)
-* [Submitting Pull Requests](#submitting-pull-requests)
-* [Developers Certificate of Origin](#developers-certificate-of-origin)
-* [Code of Conduct](#code-of-conduct)
+- [Code of Conduct](#code-of-conduct)
+- [Getting Started](#getting-started)
+ - [Development Setup](#development-setup)
+ - [Project Structure](#project-structure)
+- [How Can I Contribute?](#how-can-i-contribute)
+ - [Reporting Bugs](#reporting-bugs)
+ - [Suggesting Features](#suggesting-features)
+ - [Improving Documentation](#improving-documentation)
+ - [Submitting Code](#submitting-code)
+- [Development Workflow](#development-workflow)
+ - [Branching Strategy](#branching-strategy)
+ - [Commit Guidelines](#commit-guidelines)
+ - [Pull Request Process](#pull-request-process)
+- [Coding Standards](#coding-standards)
+ - [Swift Style Guide](#swift-style-guide)
+ - [Code Quality](#code-quality)
+ - [Testing Requirements](#testing-requirements)
+- [Community](#community)
-## Reporting Issues
+## Code of Conduct
-A great way to contribute to the project is to send a detailed issue when you encounter a problem. We always appreciate a well-written, thorough bug report.
+This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to nv3212@gmail.com.
-Check that the project issues database doesn't already include that problem or suggestion before submitting an issue. If you find a match, feel free to vote for the issue by adding a reaction. Doing this helps prioritize the most common problems and requests.
+## Getting Started
-When reporting issues, please fill out our issue template. The information the template asks for will help us review and fix your issue faster.
+### Development Setup
-## Submitting Pull Requests
+1. **Fork the repository**
+ ```bash
+ # Click the "Fork" button on GitHub
+ ```
-You can contribute by fixing bugs or adding new features. For larger code changes, we recommend first discussing your ideas on our [GitHub Discussions](https://github.com/space-code/typhoon/discussions). When submitting a pull request, please add relevant tests and ensure your changes don't break any existing tests.
+2. **Clone your fork**
+ ```bash
+ git clone https://github.com/YOUR_USERNAME/typhoon.git
+ cd typhoon
+ ```
-## Developer's Certificate of Origin 1.1
+3. **Set up the development environment**
+ ```bash
+ # Bootstrap the project
+ make bootstrap
+ ```
-By making a contribution to this project, I certify that:
+4. **Create a feature branch**
+ ```bash
+ git checkout -b feature/your-feature-name
+ ```
-- (a) The contribution was created in whole or in part by me and I
- have the right to submit it under the open source license
- indicated in the file; or
+5. **Open the project in Xcode**
+ ```bash
+ open Package.swift
+ ```
-- (b) The contribution is based upon previous work that, to the best
- of my knowledge, is covered under an appropriate open source
- license and I have the right under that license to submit that
- work with modifications, whether created in whole or in part
- by me, under the same open source license (unless I am
- permitted to submit under a different license), as indicated
- in the file; or
+## How Can I Contribute?
-- (c) The contribution was provided directly to me by some other
- person who certified (a), (b) or (c) and I have not modified
- it.
+### Reporting Bugs
-- (d) I understand and agree that this project and the contribution
- are public and that a record of the contribution (including all
- personal information I submit with it, including my sign-off) is
- maintained indefinitely and may be redistributed consistent with
- this project or the open source license(s) involved.
+Before creating a bug report, please check the [existing issues](https://github.com/space-code/typhoon/issues) to avoid duplicates.
-## Code of Conduct
+When creating a bug report, include:
+
+- **Clear title** - Describe the issue concisely
+- **Reproduction steps** - Detailed steps to reproduce the bug
+- **Expected behavior** - What you expected to happen
+- **Actual behavior** - What actually happened
+- **Environment** - OS, Xcode version, Swift version
+- **Code samples** - Minimal reproducible example
+- **Error messages** - Complete error output if applicable
+
+**Example:**
+```markdown
+**Title:** ExponentialWithJitter strategy returns incorrect delay
+
+**Steps to reproduce:**
+1. Create RetryPolicyService with exponentialWithJitter strategy
+2. Set maxInterval to 30 seconds
+3. Observe delays exceeding maxInterval
+
+**Expected:** Delays should never exceed 30 seconds
+**Actual:** Delays can reach 60+ seconds
+
+**Environment:**
+- iOS 16.0
+- Xcode 15.3
+- Swift 5.10
+
+**Code:**
+\`\`\`swift
+let service = RetryPolicyService(
+ strategy: .exponentialWithJitter(
+ retry: 5,
+ maxInterval: .seconds(30),
+ duration: .seconds(1)
+ )
+)
+\`\`\`
+```
+
+### Suggesting Features
+
+We love feature suggestions! When proposing a new feature, include:
+
+- **Problem statement** - What problem does this solve?
+- **Proposed solution** - How should it work?
+- **Alternatives** - What alternatives did you consider?
+- **Use cases** - Real-world scenarios
+- **API design** - Example code showing usage
+- **Breaking changes** - Will this break existing code?
+
+**Example:**
+```markdown
+**Feature:** Add adaptive retry strategy
+
+**Problem:** Current strategies are static. Applications need dynamic adjustment based on error patterns.
+
+**Solution:** Add `.adaptive()` strategy that adjusts backoff based on success/failure rates.
+
+**API:**
+\`\`\`swift
+let service = RetryPolicyService(
+ strategy: .adaptive(
+ minRetry: 2,
+ maxRetry: 10,
+ adaptiveFactor: 1.5
+ )
+)
+\`\`\`
+
+**Use case:** Mobile app that needs aggressive retries on good connections but conservative retries on poor connections.
+```
+
+### Improving Documentation
+
+Documentation improvements are always welcome:
+
+- **Code comments** - Add/improve inline documentation
+- **DocC documentation** - Enhance documentation articles
+- **README** - Fix typos, add examples
+- **Guides** - Write tutorials or how-to guides
+- **API documentation** - Document public APIs
+
+### Submitting Code
+
+1. **Check existing work** - Look for related issues or PRs
+2. **Discuss major changes** - Open an issue for large features
+3. **Follow coding standards** - See [Coding Standards](#coding-standards)
+4. **Write tests** - All code changes require tests
+5. **Update documentation** - Keep docs in sync with code
+6. **Create a pull request** - Use clear description
-The Code of Conduct governs how we behave in public or in private
-whenever the project will be judged by our actions.
-We expect it to be honored by everyone who contributes to this project.
+## Development Workflow
-See [CODE_OF_CONDUCT.md](https://github.com/space-code/typhoon/blob/master/CODE_OF_CONDUCT.md) for details.
+### Branching Strategy
+
+We use a simplified branching model:
+
+- **`main`** - Main development branch (all PRs target this)
+- **`feature/*`** - New features
+- **`fix/*`** - Bug fixes
+- **`docs/*`** - Documentation updates
+- **`refactor/*`** - Code refactoring
+- **`test/*`** - Test improvements
+
+**Branch naming examples:**
+```bash
+feature/adaptive-retry-strategy
+fix/exponential-jitter-calculation
+docs/update-quick-start-guide
+refactor/simplify-delay-calculation
+test/add-retry-sequence-tests
+```
+
+### Commit Guidelines
+
+We use [Conventional Commits](https://www.conventionalcommits.org/) for clear, structured commit history.
+
+**Format:**
+```
+():
+
+
+
+
## Description
-`Typhoon` is a service for retry policies.
+Typhoon is a modern, lightweight Swift framework that provides elegant and robust retry policies for asynchronous operations. Built with Swift's async/await concurrency model, it helps you handle transient failures gracefully with configurable retry strategies.
+
+## Features
+
+✨ **Multiple Retry Strategies** - Constant, exponential, and exponential with jitter
+⚡ **Async/Await Native** - Built for modern Swift concurrency
+🎯 **Type-Safe** - Leverages Swift's type system for compile-time safety
+🔧 **Configurable** - Flexible retry parameters for any use case
+📱 **Cross-Platform** - Works on iOS, macOS, tvOS, watchOS, and visionOS
+⚡ **Lightweight** - Minimal footprint with zero dependencies
+🧪 **Well Tested** - Comprehensive test coverage
+
+## Table of Contents
-- [Usage](#usage)
- [Requirements](#requirements)
- [Installation](#installation)
+- [Quick Start](#quick-start)
+- [Usage](#usage)
+ - [Retry Strategies](#retry-strategies)
+ - [Constant Strategy](#constant-strategy)
+ - [Exponential Strategy](#exponential-strategy)
+ - [Exponential with Jitter Strategy](#exponential-with-jitter-strategy)
+- [Common Use Cases](#common-use-cases)
- [Communication](#communication)
+- [Documentation](#documentation)
- [Contributing](#contributing)
+ - [Development Setup](#development-setup)
- [Author](#author)
- [License](#license)
+## Requirements
+
+| Platform | Minimum Version |
+|-----------|----------------|
+| iOS | 13.0+ |
+| macOS | 10.15+ |
+| tvOS | 13.0+ |
+| watchOS | 6.0+ |
+| visionOS | 1.0+ |
+| Xcode | 15.3+ |
+| Swift | 5.10+ |
+
+## Installation
+
+### Swift Package Manager
+
+Add the following dependency to your `Package.swift`:
+
+```swift
+dependencies: [
+ .package(url: "https://github.com/space-code/typhoon.git", from: "1.3.0")
+]
+```
+
+Or add it through Xcode:
+
+1. File > Add Package Dependencies
+2. Enter package URL: `https://github.com/space-code/typhoon.git`
+3. Select version requirements
+
+## Quick Start
+
+```swift
+import Typhoon
+
+let retryService = RetryPolicyService(
+ strategy: .constant(retry: 3, duration: .seconds(1))
+)
+
+do {
+ let result = try await retryService.retry {
+ try await fetchDataFromAPI()
+ }
+ print("✅ Success: \(result)")
+} catch {
+ print("❌ Failed after retries: \(error)")
+}
+```
+
## Usage
-`Typhoon` provides three retry policy strategies:
+### Retry Strategies
+
+Typhoon provides three powerful retry strategies to handle different failure scenarios:
```swift
/// A retry strategy with a constant number of attempts and fixed duration between retries.
@@ -34,60 +105,231 @@ case constant(retry: Int, duration: DispatchTimeInterval)
case exponential(retry: Int, multiplier: Double = 2.0, duration: DispatchTimeInterval)
/// A retry strategy with exponential increase in duration between retries and added jitter.
-case exponentialWithJitter(retry: Int, jitterFactor: Double = 0.1, maxInterval: UInt64? = 60, multiplier: Double = 2.0, duration: DispatchTimeInterval)
+case exponentialWithJitter(
+ retry: Int,
+ jitterFactor: Double = 0.1,
+ maxInterval: DispatchTimeInterval? = .seconds(60),
+ multiplier: Double = 2.0,
+ duration: DispatchTimeInterval
+)
```
-Create a `RetryPolicyService` instance and pass a desired strategy like this:
+### Constant Strategy
+
+Best for scenarios where you want predictable, fixed delays between retries:
```swift
import Typhoon
-let retryPolicyService = RetryPolicyService(strategy: .constant(retry: 10, duration: .seconds(1)))
+// Retry up to 5 times with 2 seconds between each attempt
+let service = RetryPolicyService(
+ strategy: .constant(retry: 5, duration: .seconds(2))
+)
do {
- _ = try await retryPolicyService.retry {
- // Some logic here ...
- }
+ let data = try await service.retry {
+ try await URLSession.shared.data(from: url)
+ }
} catch {
- // Catch an error here ...
+ print("Failed after 5 attempts")
}
```
-## Requirements
+**Retry Timeline:**
+- Attempt 1: Immediate
+- Attempt 2: After 2 seconds
+- Attempt 3: After 2 seconds
+- Attempt 4: After 2 seconds
+- Attempt 5: After 2 seconds
-- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ / visionOS 1.0+
-- Xcode 15.3
-- Swift 5.10
+### Exponential Strategy
-## Installation
-### Swift Package Manager
+Ideal for avoiding overwhelming a failing service by progressively increasing wait times:
+
+```swift
+import Typhoon
+
+// Retry up to 4 times with exponentially increasing delays
+let service = RetryPolicyService(
+ strategy: .exponential(
+ retry: 4,
+ multiplier: 2.0,
+ duration: .seconds(1)
+ )
+)
+
+do {
+ let response = try await service.retry {
+ try await performNetworkRequest()
+ }
+} catch {
+ print("Request failed after exponential backoff")
+}
+```
+
+**Retry Timeline:**
+- Attempt 1: Immediate
+- Attempt 2: After 1 second (1 × 2⁰)
+- Attempt 3: After 2 seconds (1 × 2¹)
+- Attempt 4: After 4 seconds (1 × 2²)
-The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It is in early development, but `typhoon` does support its use on supported platforms.
+### Exponential with Jitter Strategy
-Once you have your Swift package set up, adding `typhoon` as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`.
+The most sophisticated strategy, adding randomization to prevent thundering herd problems:
```swift
-dependencies: [
- .package(url: "https://github.com/space-code/typhoon.git", .upToNextMajor(from: "1.3.0"))
-]
+import Typhoon
+
+// Retry with exponential backoff, jitter, and maximum interval cap
+let service = RetryPolicyService(
+ strategy: .exponentialWithJitter(
+ retry: 5,
+ jitterFactor: 0.2, // Add ±20% randomization
+ maxInterval: .seconds(30), // Cap at 30 seconds
+ multiplier: 2.0,
+ duration: .seconds(1)
+ )
+)
+
+do {
+ let result = try await service.retry {
+ try await connectToDatabase()
+ }
+} catch {
+ print("Connection failed after sophisticated retry attempts")
+}
+```
+
+**Benefits of Jitter:**
+- Prevents multiple clients from retrying simultaneously
+- Reduces load spikes on recovering services
+- Improves overall system resilience
+
+## Common Use Cases
+
+### Network Requests
+
+```swift
+import Typhoon
+
+class APIClient {
+ private let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 3, duration: .milliseconds(500))
+ )
+
+ func fetchUser(id: String) async throws -> User {
+ try await retryService.retry {
+ let (data, _) = try await URLSession.shared.data(
+ from: URL(string: "https://api.example.com/users/\(id)")!
+ )
+ return try JSONDecoder().decode(User.self, from: data)
+ }
+ }
+}
+```
+
+### Database Operations
+
+```swift
+import Typhoon
+
+class DatabaseManager {
+ private let retryService = RetryPolicyService(
+ strategy: .exponentialWithJitter(
+ retry: 5,
+ jitterFactor: 0.15,
+ maxInterval: .seconds(60),
+ duration: .seconds(1)
+ )
+ )
+
+ func saveRecord(_ record: Record) async throws {
+ try await retryService.retry {
+ try await database.insert(record)
+ }
+ }
+}
+```
+
+### File Operations
+
+```swift
+import Typhoon
+
+class FileService {
+ private let retryService = RetryPolicyService(
+ strategy: .constant(retry: 3, duration: .milliseconds(100))
+ )
+
+ func writeFile(data: Data, to path: String) async throws {
+ try await retryService.retry {
+ try data.write(to: URL(fileURLWithPath: path))
+ }
+ }
+}
+```
+
+### Third-Party Service Integration
+
+```swift
+import Typhoon
+
+class PaymentService {
+ private let retryService = RetryPolicyService(
+ strategy: .exponential(
+ retry: 4,
+ multiplier: 1.5,
+ duration: .seconds(2)
+ )
+ )
+
+ func processPayment(amount: Decimal) async throws -> PaymentResult {
+ try await retryService.retry {
+ try await paymentGateway.charge(amount: amount)
+ }
+ }
+}
```
## Communication
-- If you **found a bug**, open an issue.
-- If you **have a feature request**, open an issue.
-- If you **want to contribute**, submit a pull request.
+
+- 🐛 **Found a bug?** [Open an issue](https://github.com/space-code/typhoon/issues/new)
+- 💡 **Have a feature request?** [Open an issue](https://github.com/space-code/typhoon/issues/new)
+- ❓ **Questions?** [Start a discussion](https://github.com/space-code/typhoon/discussions)
+- 🔒 **Security issue?** Email nv3212@gmail.com
+
+## Documentation
+
+Comprehensive documentation is available: [Typhoon Documentation](https://space-code.github.io/typhoon/typhoon/documentation/typhoon/)
## Contributing
-Bootstrapping development environment
-```
+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.
+
+### Development Setup
+
+Bootstrap the development environment:
+
+```bash
make bootstrap
```
-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!
-
## Author
-Nikita Vasilev, nv3212@gmail.com
+
+**Nikita Vasilev**
+- Email: nv3212@gmail.com
+- GitHub: [@ns-vasilev](https://github.com/ns-vasilev)
## License
-typhoon is available under the MIT license. See the LICENSE file for more info.
+
+Typhoon is released under the MIT license. See [LICENSE](https://github.com/space-code/typhoon/blob/main/LICENSE) for details.
+
+---
+
+
+
+**[⬆ back to top](#typhoon)**
+
+Made with ❤️ by [space-code](https://github.com/space-code)
+
+
\ No newline at end of file
diff --git a/Sources/Typhoon/Classes/Extensions/DispatchTimeInterval+Double.swift b/Sources/Typhoon/Classes/Extensions/DispatchTimeInterval+Double.swift
index 1cc4d74..6f3d5f0 100644
--- a/Sources/Typhoon/Classes/Extensions/DispatchTimeInterval+Double.swift
+++ b/Sources/Typhoon/Classes/Extensions/DispatchTimeInterval+Double.swift
@@ -6,6 +6,20 @@
import Foundation
extension DispatchTimeInterval {
+ /// Converts a `DispatchTimeInterval` value into seconds represented as `Double`.
+ ///
+ /// This computed property normalizes all supported `DispatchTimeInterval` cases
+ /// (`seconds`, `milliseconds`, `microseconds`, `nanoseconds`) into a single
+ /// unit — **seconds** — which simplifies time calculations and conversions.
+ ///
+ /// For example:
+ /// - `.seconds(2)` → `2.0`
+ /// - `.milliseconds(500)` → `0.5`
+ /// - `.microseconds(1_000)` → `0.001`
+ /// - `.nanoseconds(1_000_000_000)` → `1.0`
+ ///
+ /// - Returns: The interval expressed in seconds as `Double`,
+ /// or `nil` if the interval represents `.never` or an unknown case.
var double: Double? {
switch self {
case let .seconds(value):
diff --git a/Sources/Typhoon/Classes/RetryPolicyService/IRetryPolicyService.swift b/Sources/Typhoon/Classes/RetryPolicyService/IRetryPolicyService.swift
index 9c6b570..ad94cb1 100644
--- a/Sources/Typhoon/Classes/RetryPolicyService/IRetryPolicyService.swift
+++ b/Sources/Typhoon/Classes/RetryPolicyService/IRetryPolicyService.swift
@@ -19,7 +19,7 @@ public protocol IRetryPolicyService: Sendable {
/// - Returns: The result of the closure's execution after retrying based on the policy.
func retry(
strategy: RetryPolicyStrategy?,
- onFailure: (@Sendable (Error) async -> Void)?,
+ onFailure: (@Sendable (Error) async -> Bool)?,
_ closure: @Sendable () async throws -> T
) async throws -> T
}
@@ -45,4 +45,15 @@ public extension IRetryPolicyService {
func retry(strategy: RetryPolicyStrategy?, _ closure: @Sendable () async throws -> T) async throws -> T {
try await retry(strategy: strategy, onFailure: nil, closure)
}
+
+ /// Retries a closure with a given strategy.
+ ///
+ /// - Parameters:
+ /// - onFailure: An optional closure called on each failure to handle or log errors.
+ /// - closure: The closure that will be retried based on the specified strategy.
+ ///
+ /// - Returns: The result of the closure's execution after retrying based on the policy.
+ func retry(_ closure: @Sendable () async throws -> T, onFailure: (@Sendable (Error) async -> Bool)?) async throws -> T {
+ try await retry(strategy: nil, onFailure: onFailure, closure)
+ }
}
diff --git a/Sources/Typhoon/Classes/RetryPolicyService/RetryPolicyService.swift b/Sources/Typhoon/Classes/RetryPolicyService/RetryPolicyService.swift
index 3a69e71..5ebbcee 100644
--- a/Sources/Typhoon/Classes/RetryPolicyService/RetryPolicyService.swift
+++ b/Sources/Typhoon/Classes/RetryPolicyService/RetryPolicyService.swift
@@ -27,9 +27,17 @@ public final class RetryPolicyService {
// MARK: IRetryPolicyService
extension RetryPolicyService: IRetryPolicyService {
+ /// Retries a closure with a given strategy.
+ ///
+ /// - Parameters:
+ /// - strategy: The strategy defining the behavior of the retry policy.
+ /// - onFailure: An optional closure called on each failure to handle or log errors.
+ /// - closure: The closure that will be retried based on the specified strategy.
+ ///
+ /// - Returns: The result of the closure's execution after retrying based on the policy.
public func retry(
strategy: RetryPolicyStrategy?,
- onFailure: (@Sendable (Error) async -> Void)?,
+ onFailure: (@Sendable (Error) async -> Bool)?,
_ closure: @Sendable () async throws -> T
) async throws -> T {
for duration in RetrySequence(strategy: strategy ?? self.strategy) {
@@ -38,7 +46,11 @@ extension RetryPolicyService: IRetryPolicyService {
do {
return try await closure()
} catch {
- await onFailure?(error)
+ let shouldContinue = await onFailure?(error) ?? true
+
+ if !shouldContinue {
+ throw error
+ }
}
try await Task.sleep(nanoseconds: duration)
diff --git a/Sources/Typhoon/Classes/RetrySequence/Iterator/RetryIterator.swift b/Sources/Typhoon/Classes/RetrySequence/Iterator/RetryIterator.swift
index 060f8cc..ddee6d6 100644
--- a/Sources/Typhoon/Classes/RetrySequence/Iterator/RetryIterator.swift
+++ b/Sources/Typhoon/Classes/RetrySequence/Iterator/RetryIterator.swift
@@ -7,21 +7,54 @@ import Foundation
// MARK: - RetryIterator
+/// An iterator that generates retry delays according to a retry policy strategy.
+///
+/// `RetryIterator` conforms to `IteratorProtocol` and produces a sequence of delay
+/// values (in nanoseconds) that can be used to schedule retry attempts for
+/// asynchronous operations such as network requests or background tasks.
+///
+/// Each call to `next()` returns the delay for the current retry attempt and then
+/// advances the internal retry counter. When the maximum number of retries defined
+/// by the strategy is reached, the iterator stops producing values.
struct RetryIterator: IteratorProtocol {
// MARK: Properties
+ /// The current retry attempt index.
+ ///
+ /// Starts from `0` and is incremented after each successful call to `next()`.
+ /// This value is used when calculating exponential backoff delays.
private var retries: UInt = 0
+ /// The retry policy strategy that defines:
+ /// - The maximum number of retry attempts.
+ /// - The algorithm used to calculate delays between retries
+ /// (constant, exponential, or exponential with jitter).
private let strategy: RetryPolicyStrategy
// MARK: Initialization
+ /// Creates a new `RetryIterator` with the specified retry policy strategy.
+ ///
+ /// - Parameter strategy: A `RetryPolicyStrategy` describing how retry delays
+ /// should be calculated and how many retries are allowed.
init(strategy: RetryPolicyStrategy) {
self.strategy = strategy
}
// MARK: IteratorProtocol
+ /// Returns the delay for the next retry attempt.
+ ///
+ /// The delay is calculated according to the current retry policy strategy
+ /// and expressed in **nanoseconds**, making it suitable for use with
+ /// `DispatchQueue`, `Task.sleep`, or other low-level scheduling APIs.
+ ///
+ /// After the delay is calculated, the internal retry counter is incremented.
+ /// When the maximum number of retries is exceeded, this method returns `nil`,
+ /// signaling the end of the iteration.
+ ///
+ /// - Returns: The delay in nanoseconds for the current retry attempt,
+ /// or `nil` if no more retries are allowed.
mutating func next() -> UInt64? {
guard isValid() else { return nil }
@@ -32,10 +65,22 @@ struct RetryIterator: IteratorProtocol {
// MARK: Private
+ /// Determines whether another retry attempt is allowed.
+ ///
+ /// This method compares the current retry count with the maximum
+ /// number of retries defined in the retry strategy.
+ ///
+ /// - Returns: `true` if another retry attempt is allowed;
+ /// `false` otherwise.
private func isValid() -> Bool {
retries < strategy.retries
}
+ /// Calculates the delay for the current retry attempt
+ /// based on the selected retry strategy.
+ ///
+ /// - Returns: The computed delay in nanoseconds, or `0`
+ /// if the duration cannot be converted to seconds.
private func delay() -> UInt64? {
switch strategy {
case let .constant(_, duration):
@@ -61,11 +106,26 @@ struct RetryIterator: IteratorProtocol {
// MARK: - Helper Methods
+ /// Converts a `DispatchTimeInterval` to nanoseconds.
+ ///
+ /// - Parameter duration: The time interval to convert.
+ /// - Returns: The equivalent duration in nanoseconds, or `0`
+ /// if the interval cannot be represented as seconds.
private func convertToNanoseconds(_ duration: DispatchTimeInterval) -> UInt64? {
guard let seconds = duration.double else { return .zero }
return safeConvertToUInt64(seconds * .nanosec)
}
+ /// Calculates an exponential backoff delay without jitter.
+ ///
+ /// The delay is calculated as:
+ /// `baseDelay * multiplier ^ retries`
+ ///
+ /// - Parameters:
+ /// - duration: The base delay value.
+ /// - multiplier: The exponential growth multiplier.
+ /// - retries: The current retry attempt index.
+ /// - Returns: The calculated delay in nanoseconds.
private func calculateExponentialDelay(
duration: DispatchTimeInterval,
multiplier: Double,
@@ -79,6 +139,20 @@ struct RetryIterator: IteratorProtocol {
return safeConvertToUInt64(value)
}
+ /// Calculates an exponential backoff delay with jitter and an optional maximum interval.
+ ///
+ /// This method:
+ /// 1. Calculates the exponential backoff delay.
+ /// 2. Applies a random jitter to spread retry attempts over time.
+ /// 3. Caps the result at the provided maximum interval, if any.
+ ///
+ /// - Parameters:
+ /// - duration: The base delay value.
+ /// - multiplier: The exponential growth multiplier.
+ /// - retries: The current retry attempt index.
+ /// - jitterFactor: The percentage of randomness applied to the delay.
+ /// - maxInterval: An optional upper bound for the delay.
+ /// - Returns: The final delay in nanoseconds.
private func calculateExponentialDelayWithJitter(
duration: DispatchTimeInterval,
multiplier: Double,
@@ -107,6 +181,10 @@ struct RetryIterator: IteratorProtocol {
return safeConvertToUInt64(min(delayWithJitter, maxDelayNanos))
}
+ /// Calculates the maximum allowed delay in nanoseconds.
+ ///
+ /// - Parameter maxInterval: An optional maximum delay value.
+ /// - Returns: The maximum delay in nanoseconds, clamped to `UInt64.max`.
private func calculateMaxDelay(_ maxInterval: DispatchTimeInterval?) -> Double {
guard let maxSeconds = maxInterval?.double else {
return Double(UInt64.max)
@@ -116,6 +194,16 @@ struct RetryIterator: IteratorProtocol {
return min(maxNanos, Double(UInt64.max))
}
+ /// Applies random jitter to a delay value.
+ ///
+ /// Jitter helps prevent synchronized retries (the "thundering herd" problem)
+ /// by randomizing retry timings within a defined range.
+ ///
+ /// - Parameters:
+ /// - value: The base delay value in nanoseconds.
+ /// - factor: The jitter factor defining the randomization range.
+ /// - maxDelay: The maximum allowed delay.
+ /// - Returns: A jittered delay value clamped to valid bounds.
private func applyJitter(
to value: Double,
factor: Double,
diff --git a/Sources/Typhoon/Typhoon.docc/Articles/advanced-retry-strategies.md b/Sources/Typhoon/Typhoon.docc/Articles/advanced-retry-strategies.md
new file mode 100644
index 0000000..9bcd403
--- /dev/null
+++ b/Sources/Typhoon/Typhoon.docc/Articles/advanced-retry-strategies.md
@@ -0,0 +1,381 @@
+
+# Advanced Retry Strategies
+
+Master advanced retry patterns and optimization techniques.
+
+## Overview
+
+This guide covers advanced usage patterns, performance optimization, and sophisticated retry strategies for complex scenarios.
+
+## Strategy Deep Dive
+
+### Understanding Exponential Backoff
+
+Exponential backoff progressively increases wait times to avoid overwhelming recovering services:
+
+```swift
+let strategy = RetryStrategy.exponential(
+ retry: 5,
+ multiplier: 2.0,
+ duration: .seconds(1)
+)
+```
+
+**Calculation:** `delay = baseDuration × multiplier^retryCount`
+
+| Attempt | Calculation | Delay |
+|---------|-------------|-------|
+| 1 | 1 × 2⁰ | 1s |
+| 2 | 1 × 2¹ | 2s |
+| 3 | 1 × 2² | 4s |
+| 4 | 1 × 2³ | 8s |
+| 5 | 1 × 2⁴ | 16s |
+
+**Multiplier effects:**
+
+```swift
+// Aggressive backoff (multiplier: 3.0)
+// 1s → 3s → 9s → 27s → 81s
+
+// Moderate backoff (multiplier: 1.5)
+// 1s → 1.5s → 2.25s → 3.375s → 5.0625s
+
+// Slow backoff (multiplier: 1.2)
+// 1s → 1.2s → 1.44s → 1.728s → 2.074s
+```
+
+### Jitter: Preventing Thundering Herd
+
+When multiple clients retry simultaneously, they can overwhelm a recovering service. Jitter adds randomization to prevent this:
+
+```swift
+let strategy = RetryStrategy.exponentialWithJitter(
+ retry: 5,
+ jitterFactor: 0.2, // ±20% randomization
+ maxInterval: .seconds(30), // Cap at 30 seconds
+ multiplier: 2.0,
+ duration: .seconds(1)
+)
+```
+
+**Without jitter:**
+```
+Client 1: 0s → 1s → 2s → 4s → 8s
+Client 2: 0s → 1s → 2s → 4s → 8s
+Client 3: 0s → 1s → 2s → 4s → 8s
+All hit server simultaneously! 💥
+```
+
+**With jitter:**
+```
+Client 1: 0s → 0.9s → 2.1s → 3.8s → 8.2s
+Client 2: 0s → 1.1s → 1.9s → 4.3s → 7.7s
+Client 3: 0s → 0.8s → 2.2s → 3.9s → 8.1s
+Traffic spread out! ✅
+```
+
+### Maximum Interval Capping
+
+Prevent delays from growing unbounded:
+
+```swift
+.exponentialWithJitter(
+ retry: 10,
+ jitterFactor: 0.1,
+ maxInterval: .seconds(60), // Never wait more than 60 seconds
+ multiplier: 2.0,
+ duration: .seconds(1)
+)
+```
+
+**Without cap:** 1s → 2s → 4s → 8s → 16s → 32s → 64s → 128s → 256s...
+
+**With 60s cap:** 1s → 2s → 4s → 8s → 16s → 32s → 60s → 60s → 60s...
+
+## Advanced Patterns
+
+### Conditional Retry Logic
+
+Retry only for specific error types:
+
+```swift
+enum NetworkError: Error {
+ case serverError
+ case clientError
+ case timeout
+ case connectionLost
+}
+
+func fetchWithConditionalRetry() async throws -> Data {
+ let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 3, duration: .seconds(1))
+ )
+
+ do {
+
+ return try await retryService.retry({
+ try await performRequest()
+ }, onFailure: { error in
+ if let error = error as? NetworkError {
+ switch error {
+ case .serverError, .timeout, .connectionLost:
+ // These errors were already retried
+ return true
+ case .clientError:
+ // Don't retry client errors (4xx)
+ return false
+ }
+ }
+
+ return true
+ })
+ } catch let error as RetryPolicyError {
+ switch error {
+ case .retryLimitExceeded:
+ // Retry linit exceeded
+ throw error
+ }
+ }
+}
+```
+
+### Retry with Timeout
+
+Combine retry logic with overall timeout:
+
+```swift
+func fetchWithTimeout() async throws -> Data {
+ let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 5, duration: .seconds(1))
+ )
+
+ return try await withThrowingTaskGroup(of: Data.self) { group in
+ // Add retry task
+ group.addTask {
+ try await retryService.retry {
+ try await performRequest()
+ }
+ }
+
+ // Add timeout task
+ group.addTask {
+ try await Task.sleep(nanoseconds: 30_000_000_000) // 30 seconds
+ throw TimeoutError.exceeded
+ }
+
+ // Return first result or throw first error
+ guard let result = try await group.next() else {
+ throw TimeoutError.unknown
+ }
+
+ group.cancelAll()
+ return result
+ }
+}
+```
+
+### Adaptive Retry Strategy
+
+Adjust strategy based on error patterns:
+
+```swift
+actor AdaptiveRetryService {
+ private var consecutiveFailures = 0
+ private let maxConsecutiveFailures = 3
+
+ func retry(_ operation: @Sendable () async throws -> T) async throws -> T {
+ let strategy = selectStrategy()
+ let retryService = RetryPolicyService(strategy: strategy)
+
+ do {
+ let result = try await retryService.retry(operation)
+ consecutiveFailures = 0
+ return result
+ } catch {
+ consecutiveFailures += 1
+ throw error
+ }
+ }
+
+ private func selectStrategy() -> RetryPolicyStrategy {
+ if consecutiveFailures >= maxConsecutiveFailures {
+ // System under stress - use conservative strategy
+ return .exponentialWithJitter(
+ retry: 3,
+ jitterFactor: 0.3,
+ maxInterval: 120,
+ multiplier: 3.0,
+ duration: .seconds(5)
+ )
+ } else {
+ // Normal operation - use standard strategy
+ return .exponential(
+ retry: 4,
+ multiplier: 2.0,
+ duration: .seconds(1)
+ )
+ }
+ }
+}
+```
+
+### Retry with Progress Tracking
+
+Monitor retry attempts:
+
+```swift
+actor RetryProgressTracker {
+ var onRetry: ((Int, Error) async -> Void)?
+
+ private let maxRetries = 5
+
+ private final class AttemptCounter {
+ var count: Int = 0
+ }
+
+ func fetchWithProgress(
+ operation: @escaping () async throws -> T
+ ) async throws -> T {
+
+ let counter = AttemptCounter()
+
+ let retryService = RetryPolicyService(
+ strategy: .exponential(retry: maxRetries, duration: .seconds(1))
+ )
+
+ return try await retryService.retry {
+ counter.count += 1
+
+ do {
+ return try await operation()
+ } catch {
+ if counter.count < self.maxRetries {
+ await self.notifyOnRetry(attemptCount: counter.count, error: error)
+ }
+
+ throw error
+ }
+ }
+ }
+
+ private func notifyOnRetry(attemptCount: Int, error: Error) async {
+ await onRetry?(attemptCount, error)
+ }
+}
+
+// Usage
+let tracker = RetryProgressTracker()
+tracker.onRetry = { attempt, error in
+ print("Retry attempt \(attempt) after error: \(error)")
+}
+
+let data = try await tracker.fetchWithProgress {
+ try await fetchFromAPI()
+}
+```
+
+## Performance Optimization
+
+### Choosing the Right Strategy
+
+| Scenario | Strategy | Rationale |
+|----------|----------|-----------|
+| Fast local operations | Constant (3, 100ms) | Quick retries for transient issues |
+| Network requests | Exponential (4, 500ms-1s) | Give service time to recover |
+| High-traffic APIs | Exponential with jitter | Prevent synchronized retries |
+| Critical operations | Exponential with jitter + cap | Balance persistence with resource usage |
+| Rate-limited APIs | Constant with long delay | Respect rate limits |
+
+### Memory Management
+
+Typhoon is designed to be memory-efficient:
+
+```swift
+// ✅ Good - Reuse service instance
+class DataRepository {
+ private let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 3, duration: .seconds(1))
+ )
+
+ func fetchData() async throws -> Data {
+ try await retryService.retry {
+ try await performFetch()
+ }
+ }
+}
+
+// ❌ Avoid - Creating new instances repeatedly
+func fetchData() async throws -> Data {
+ let retryService = RetryPolicyService( // Creates new instance each time
+ strategy: .exponential(retry: 3, duration: .seconds(1))
+ )
+ return try await retryService.retry {
+ try await performFetch()
+ }
+}
+```
+
+### Cancellation Support
+
+Typhoon respects task cancellation:
+
+```swift
+let task = Task {
+ let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 10, duration: .seconds(5))
+ )
+
+ return try await retryService.retry {
+ try Task.checkCancellation() // Respects cancellation
+ return try await longRunningOperation()
+ }
+}
+
+// Cancel after 10 seconds
+Task {
+ try await Task.sleep(nanoseconds: 10_000_000_000)
+ task.cancel()
+}
+```
+
+## Testing Strategies
+
+### Mock Retry Behavior
+
+```swift
+class MockRetryService {
+ var shouldSucceedAfter: Int = 2
+ private var attemptCount = 0
+
+ func simulateRetry() async throws -> String {
+ attemptCount += 1
+
+ if attemptCount < shouldSucceedAfter {
+ throw MockError.transient
+ }
+
+ return "Success"
+ }
+}
+
+// Test
+let mock = MockRetryService()
+mock.shouldSucceedAfter = 3
+
+let retryService = RetryPolicyService(
+ strategy: .constant(retry: 5, duration: .milliseconds(10))
+)
+
+let result = try await retryService.retry {
+ try await mock.simulateRetry()
+}
+
+XCTAssertEqual(result, "Success")
+```
+
+## See Also
+
+-
+-
+
+- ``RetryPolicyService``
diff --git a/Sources/Typhoon/Typhoon.docc/Articles/best-practices.md b/Sources/Typhoon/Typhoon.docc/Articles/best-practices.md
new file mode 100644
index 0000000..3ae1f8a
--- /dev/null
+++ b/Sources/Typhoon/Typhoon.docc/Articles/best-practices.md
@@ -0,0 +1,363 @@
+# Best Practices
+
+Learn the recommended patterns and practices for using Typhoon effectively.
+
+## Overview
+
+This guide covers best practices, common pitfalls, and recommended patterns for implementing retry logic in production applications.
+
+## Strategy Selection
+
+### Choose Based on Use Case
+
+Different scenarios require different retry strategies:
+
+```swift
+// ✅ Fast local operations (file I/O, cache)
+let localRetry = RetryPolicyService(
+ strategy: .constant(retry: 3, duration: .milliseconds(100))
+)
+
+// ✅ Standard API calls
+let apiRetry = RetryPolicyService(
+ strategy: .exponential(retry: 4, multiplier: 2.0, duration: .seconds(1))
+)
+
+// ✅ High-traffic services
+let highTrafficRetry = RetryPolicyService(
+ strategy: .exponentialWithJitter(
+ retry: 5,
+ jitterFactor: 0.2,
+ maxInterval: 60,
+ multiplier: 2.0,
+ duration: .seconds(1)
+ )
+)
+
+// ❌ Wrong - Too many retries for quick operation
+let badRetry = RetryPolicyService(
+ strategy: .exponential(retry: 20, duration: .seconds(10))
+)
+```
+
+### Recommended Configurations
+
+| Operation Type | Strategy | Retry Count | Base Duration | Notes |
+|---------------|----------|-------------|---------------|-------|
+| Cache access | Constant | 2-3 | 50-100ms | Fast recovery |
+| Database query | Constant | 3-5 | 100-500ms | Predictable delays |
+| REST API | Exponential | 3-4 | 500ms-1s | Standard backoff |
+| GraphQL API | Exponential | 3-5 | 1-2s | Handle complex queries |
+| File upload | Exponential + Jitter | 5-7 | 2-5s | Large operations |
+| Critical payment | Exponential + Jitter | 5-10 | 1-2s | Maximum reliability |
+| Rate-limited API | Constant | 3-5 | Based on rate limit | Respect limits |
+
+## Service Architecture
+
+### Reuse Service Instances
+
+Create retry services at the appropriate scope:
+
+```swift
+// ✅ Good - Singleton or service property
+class APIClient {
+ static let shared = APIClient()
+
+ private let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 3, duration: .seconds(1))
+ )
+
+ func fetchUser(id: String) async throws -> User {
+ try await retryService.retry {
+ try await performFetch(id: id)
+ }
+ }
+}
+
+// ✅ Good - Dependency injection
+class DataRepository {
+ private let retryService: RetryPolicyService
+
+ init(retryService: RetryPolicyService = .default) {
+ self.retryService = retryService
+ }
+}
+
+// ❌ Bad - Creating new instances repeatedly
+func fetchData() async throws -> Data {
+ let service = RetryPolicyService(...) // Don't do this!
+ return try await service.retry { ... }
+}
+```
+
+### Organize by Layer
+
+Structure retry services by architectural layer:
+
+```swift
+// Network Layer
+class NetworkRetryService {
+ static let standard = RetryPolicyService(
+ strategy: .exponential(retry: 3, duration: .milliseconds(500))
+ )
+
+ static let critical = RetryPolicyService(
+ strategy: .exponentialWithJitter(
+ retry: 5,
+ jitterFactor: 0.2,
+ maxInterval: 60,
+ multiplier: 2.0,
+ duration: .seconds(1)
+ )
+ )
+}
+
+// Data Layer
+class DataRetryService {
+ static let cache = RetryPolicyService(
+ strategy: .constant(retry: 2, duration: .milliseconds(50))
+ )
+
+ static let database = RetryPolicyService(
+ strategy: .constant(retry: 3, duration: .milliseconds(200))
+ )
+}
+
+// Usage
+class UserRepository {
+ func fetchUser(id: String) async throws -> User {
+ try await NetworkRetryService.standard.retry {
+ try await api.getUser(id: id)
+ }
+ }
+
+ func cacheUser(_ user: User) async throws {
+ try await DataRetryService.cache.retry {
+ try cache.save(user)
+ }
+ }
+}
+```
+
+## Error Handling
+
+### Selective Retry
+
+Don't retry errors that won't benefit from retrying:
+
+```swift
+enum APIError: Error {
+ case networkFailure // ✅ Retry
+ case serverError // ✅ Retry (5xx)
+ case timeout // ✅ Retry
+ case rateLimited // ✅ Retry with delay
+ case unauthorized // ❌ Don't retry (401)
+ case forbidden // ❌ Don't retry (403)
+ case notFound // ❌ Don't retry (404)
+ case badRequest // ❌ Don't retry (400)
+ case invalidData // ❌ Don't retry
+}
+
+func fetchWithSelectiveRetry() async throws -> Data {
+ do {
+ return try await retryService.retry({
+ try await performRequest()
+ }, onFailure: { error in
+ if let error = error as? APIError {
+ switch error {
+ case .unauthorized, .forbidden, .notFound, .badRequest, .invalidData:
+ // Don't retry client errors
+ throw error
+ case .networkFailure, .serverError, .timeout, .rateLimited:
+ // These were already retried
+ throw error
+ }
+ })
+ } catch let error {
+ throw error
+ }
+}
+```
+
+## Performance Optimization
+
+### Avoid Over-Retrying
+
+Balance persistence with resource usage:
+
+```swift
+// ❌ Bad - Too aggressive
+let badService = RetryPolicyService(
+ strategy: .constant(retry: 100, duration: .milliseconds(10))
+)
+
+// ✅ Good - Reasonable limits
+let goodService = RetryPolicyService(
+ strategy: .exponentialWithJitter(
+ retry: 5,
+ maxInterval: 60,
+ duration: .seconds(1)
+ )
+)
+```
+
+### Set Appropriate Timeouts
+
+Combine retries with timeouts to prevent indefinite waiting:
+
+```swift
+func fetchWithTimeout(
+ timeout: TimeInterval,
+ operation: @Sendable @escaping () async throws -> T
+) async throws -> T {
+ try await withThrowingTaskGroup(of: T.self) { group in
+ group.addTask {
+ try await retryService.retry(operation)
+ }
+
+ group.addTask {
+ try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))
+ throw TimeoutError.exceeded
+ }
+
+ let result = try await group.next()!
+ group.cancelAll()
+ return result
+ }
+}
+```
+
+## Testing
+
+### Mock Retry Behavior
+
+Create testable retry scenarios:
+
+```swift
+actor MockService {
+ var failureCount: Int
+ private var currentAttempt = 0
+
+ init(failureCount: Int) {
+ self.failureCount = failureCount
+ }
+
+ func operation() throws -> String {
+ currentAttempt += 1
+ if currentAttempt <= failureCount {
+ throw MockError.transient
+ }
+ return "Success"
+ }
+}
+
+// Test
+func testRetrySucceedsAfterFailures() async throws {
+ let mock = MockService(failureCount: 2)
+ let service = RetryPolicyService(
+ strategy: .constant(retry: 3, duration: .milliseconds(10))
+ )
+
+ let result = try await service.retry {
+ try await mock.operation()
+ }
+
+ XCTAssertEqual(result, "Success")
+}
+```
+
+### Test Strategy Behavior
+
+Verify retry timing and attempts:
+
+```swift
+func testExponentialBackoff() async throws {
+ let startTime = Date()
+ var attempts = 0
+
+ do {
+ try await retryService.retry {
+ attempts += 1
+ throw TestError.failed
+ }
+ } catch {
+ // Expected to fail
+ }
+
+ let duration = Date().timeIntervalSince(startTime)
+
+ XCTAssertEqual(attempts, 4) // Initial + 3 retries
+ XCTAssertGreaterThan(duration, 7.0) // 1 + 2 + 4 seconds
+}
+```
+
+## Common Pitfalls
+
+### Don't Retry Non-Idempotent Operations
+
+Be careful with operations that shouldn't be repeated:
+
+```swift
+// ❌ Dangerous - May create duplicate payments
+func processPayment(amount: Decimal) async throws {
+ try await retryService.retry {
+ try await paymentGateway.charge(amount)
+ }
+}
+
+// ✅ Safe - Use idempotency key
+func processPayment(amount: Decimal, idempotencyKey: String) async throws {
+ try await retryService.retry {
+ try await paymentGateway.charge(
+ amount: amount,
+ idempotencyKey: idempotencyKey
+ )
+ }
+}
+```
+
+### Don't Ignore Cancellation
+
+Respect task cancellation:
+
+```swift
+// ✅ Good - Check cancellation
+try await retryService.retry {
+ try Task.checkCancellation()
+ return try await operation()
+}
+
+// ❌ Bad - Ignores cancellation
+try await retryService.retry {
+ return try await operation() // May continue after cancel
+}
+```
+
+### Don't Nest Retries
+
+Avoid multiple retry layers:
+
+```swift
+// ❌ Bad - Nested retries multiply attempts
+func fetch() async throws -> Data {
+ try await outerRetry.retry {
+ try await innerRetry.retry { // Don't do this!
+ try await actualFetch()
+ }
+ }
+}
+
+// ✅ Good - Single retry layer
+func fetch() async throws -> Data {
+ try await retryService.retry {
+ try await actualFetch()
+ }
+}
+```
+
+## See Also
+
+-
+-
+
+- ``RetryPolicyService``
diff --git a/Sources/Typhoon/Typhoon.docc/Articles/installation.md b/Sources/Typhoon/Typhoon.docc/Articles/installation.md
new file mode 100644
index 0000000..bf81aad
--- /dev/null
+++ b/Sources/Typhoon/Typhoon.docc/Articles/installation.md
@@ -0,0 +1,19 @@
+# Installation
+
+A guide to installing the Typhoon package into your Swift project using Swift Package Manager.
+
+## Swift Package Manager
+
+Add the Typhoon package to your project using Swift Package Manager:
+
+```swift
+dependencies: [
+ .package(url: "https://github.com/space-code/typhoon.git", from: "1.3.0")
+]
+```
+
+Or add it through Xcode:
+
+1. File > Add Package Dependencies
+2. Enter package URL: `https://github.com/space-code/typhoon.git`
+3. Select version requirements
diff --git a/Sources/Typhoon/Typhoon.docc/Articles/quick-start.md b/Sources/Typhoon/Typhoon.docc/Articles/quick-start.md
new file mode 100644
index 0000000..f1c13b6
--- /dev/null
+++ b/Sources/Typhoon/Typhoon.docc/Articles/quick-start.md
@@ -0,0 +1,190 @@
+# Quick Start
+
+Get up and running with Typhoon in minutes.
+
+## Overview
+
+Typhoon is a powerful retry policy framework for Swift that helps you handle transient failures gracefully. This guide will help you integrate Typhoon into your project and start using retry policies immediately.
+
+## Basic Usage
+
+### Your First Retry
+
+Import Typhoon and create a retry service with a simple constant strategy:
+
+```swift
+import Typhoon
+
+let retryService = RetryPolicyService(
+ strategy: .constant(retry: 3, duration: .seconds(1))
+)
+
+do {
+ let result = try await retryService.retry {
+ try await fetchDataFromAPI()
+ }
+ print("Success: \(result)")
+} catch {
+ print("Failed after 3 retries: \(error)")
+}
+```
+
+This will:
+- Try your operation immediately
+- If it fails, wait 1 second and retry
+- Repeat up to 3 times
+- Throw the last error if all attempts fail
+
+### Network Request Example
+
+Here's a practical example with URLSession:
+
+```swift
+import Foundation
+import Typhoon
+
+func fetchUser(id: String) async throws -> User {
+ let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 3, duration: .milliseconds(500))
+ )
+
+ return try await retryService.retry {
+ let url = URL(string: "https://api.example.com/users/\(id)")!
+ let (data, response) = try await URLSession.shared.data(from: url)
+
+ guard let httpResponse = response as? HTTPURLResponse,
+ httpResponse.statusCode == 200 else {
+ throw NetworkError.invalidResponse
+ }
+
+ return try JSONDecoder().decode(User.self, from: data)
+ }
+}
+```
+
+## Choosing a Strategy
+
+Typhoon provides three retry strategies:
+
+### Constant Strategy
+
+Best for predictable, fixed delays:
+
+```swift
+// Retry 5 times with 2 seconds between attempts
+.constant(retry: 5, duration: .seconds(2))
+```
+
+**Timeline:** 0s → 2s → 2s → 2s → 2s
+
+### Exponential Strategy
+
+Ideal for backing off from failing services:
+
+```swift
+// Retry 4 times with exponentially increasing delays
+.exponential(retry: 4, multiplier: 2.0, duration: .seconds(1))
+```
+
+**Timeline:** 0s → 1s → 2s → 4s
+
+### Exponential with Jitter
+
+Best for preventing thundering herd problems:
+
+```swift
+// Retry with exponential backoff, jitter, and cap
+.exponentialWithJitter(
+ retry: 5,
+ jitterFactor: 0.2,
+ maxInterval: .seconds(30),
+ multiplier: 2.0,
+ duration: .seconds(1)
+)
+```
+
+**Timeline:** 0s → ~1s → ~2s → ~4s → ~8s (with randomization)
+
+## Common Patterns
+
+### Wrapping in a Service Class
+
+Create a reusable service with built-in retry logic:
+
+```swift
+import Typhoon
+
+class APIClient {
+ private let retryService = RetryPolicyService(
+ strategy: .exponential(retry: 3, duration: .milliseconds(500))
+ )
+
+ func get(endpoint: String) async throws -> T {
+ try await retryService.retry {
+ let url = URL(string: "https://api.example.com/\(endpoint)")!
+ let (data, _) = try await URLSession.shared.data(from: url)
+ return try JSONDecoder().decode(T.self, from: data)
+ }
+ }
+}
+
+// Usage
+let client = APIClient()
+let user: User = try await client.get(endpoint: "users/123")
+```
+
+### Error Handling
+
+Handle specific errors after all retries are exhausted:
+
+```swift
+do {
+ let data = try await retryService.retry {
+ try await performOperation()
+ }
+ // Handle success
+} catch NetworkError.serverUnavailable {
+ print("Server is down")
+} catch NetworkError.timeout {
+ print("Request timed out")
+} catch {
+ print("Unexpected error: \(error)")
+}
+```
+
+### Configuration for Different Scenarios
+
+```swift
+// Quick operations (file I/O, cache access)
+let quickRetry = RetryPolicyService(
+ strategy: .constant(retry: 3, duration: .milliseconds(100))
+)
+
+// Network requests
+let networkRetry = RetryPolicyService(
+ strategy: .exponential(retry: 4, multiplier: 1.5, duration: .seconds(1))
+)
+
+// Critical operations (payments, data persistence)
+let criticalRetry = RetryPolicyService(
+ strategy: .exponentialWithJitter(
+ retry: 5,
+ jitterFactor: 0.15,
+ maxInterval: .seconds(60),
+ multiplier: 2.0,
+ duration: .seconds(2)
+ )
+)
+```
+
+## Next Steps
+
+Now that you have the basics, explore:
+
+- - Deep dive into retry strategies
+- - Learn best practices and patterns
+
+## See Also
+
+- ``RetryPolicyService``
+- ``RetryPolicyStrategy``
diff --git a/Sources/Typhoon/Typhoon.docc/Info.plist b/Sources/Typhoon/Typhoon.docc/Info.plist
new file mode 100644
index 0000000..881d4d4
--- /dev/null
+++ b/Sources/Typhoon/Typhoon.docc/Info.plist
@@ -0,0 +1,48 @@
+
+
+
+
+ CDAppleDefaultAvailability
+
+ Typhoon
+
+
+ name
+ visionOS
+ version
+ 1.0
+
+
+ name
+ watchOS
+ version
+ 6.0
+
+
+ name
+ iOS
+ version
+ 13.0
+
+
+ name
+ visionOS
+ version
+ 1.0
+
+
+ name
+ tvOS
+ version
+ 13.0
+
+
+ name
+ macOS
+ version
+ 10.15
+
+
+
+
+
diff --git a/Sources/Typhoon/Typhoon.docc/Overview.md b/Sources/Typhoon/Typhoon.docc/Overview.md
new file mode 100644
index 0000000..d9c6da3
--- /dev/null
+++ b/Sources/Typhoon/Typhoon.docc/Overview.md
@@ -0,0 +1,23 @@
+# ``Typhoon``
+
+@Metadata {
+ @TechnologyRoot
+}
+
+## Overview
+
+Typhoon is a modern, lightweight Swift framework that provides elegant and robust retry policies for asynchronous operations. Built with Swift's async/await concurrency model, it helps you handle transient failures gracefully with configurable retry strategies.
+
+## Topics
+
+### Essentials
+
+- ``RetryPolicyService``
+- ``RetryPolicyStrategy``
+
+### Articles
+
+-
+-
+-
+-
diff --git a/Tests/TyphoonTests/UnitTests/RetryPolicyServiceTests.swift b/Tests/TyphoonTests/UnitTests/RetryPolicyServiceTests.swift
index ad9e69b..683936e 100644
--- a/Tests/TyphoonTests/UnitTests/RetryPolicyServiceTests.swift
+++ b/Tests/TyphoonTests/UnitTests/RetryPolicyServiceTests.swift
@@ -99,7 +99,10 @@ final class RetryPolicyServiceTests: XCTestCase {
do {
_ = try await sut.retry(
strategy: .constant(retry: .retry, duration: .nanoseconds(1)),
- onFailure: { error in await errorContainer.setError(error as NSError) }
+ onFailure: { error in
+ await errorContainer.setError(error as NSError)
+ return false
+ }
) {
throw URLError(.unknown)
}