├── .bundle └── config ├── .codecov.yml ├── .github ├── CODEOWNERS └── pull_request_template.md ├── .gitignore ├── .hound.yml ├── .swiftlint.yml ├── .travis.yml ├── Brewfile ├── Brewfile.lock.json ├── CHANGELOG.md ├── DangerFile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Mini-Swift.podspec ├── Package.resolved ├── Package.swift ├── README.md ├── Rakefile ├── Scripts ├── data │ └── request.template.yml ├── update_changelog.sh └── update_docs.rb ├── Sources ├── LoggingService │ ├── LoggingService.swift │ └── String+Extensions.swift ├── Mini │ ├── Action.swift │ ├── ActionReducer.swift │ ├── Dispatcher.swift │ ├── Middleware.swift │ ├── Mini.swift │ ├── ReducerGroup.swift │ ├── Service.swift │ ├── StateType.swift │ ├── Store.swift │ └── Utils │ │ ├── Foundation │ │ ├── Dictionary+Extensions.swift │ │ ├── DispatchQueue+Extensions.swift │ │ ├── KeyPath.swift │ │ └── OptionalType.swift │ │ ├── OrderedSet.swift │ │ ├── RxSwift │ │ └── ObservableType+Extensions.swift │ │ └── SharedDictionary.swift ├── MiniPromises │ ├── Box.swift │ ├── MiniPromises.swift │ ├── Promise.swift │ └── Utils │ │ ├── Extensions │ │ ├── Dictionary+Extensions.swift │ │ └── Store+Extensions.swift │ │ ├── PayloadAction.swift │ │ └── RxSwift │ │ ├── ObservableType+Extensions.swift │ │ └── PrimitiveSequenceType+Extensions.swift ├── MiniTasks │ ├── MiniTasks.swift │ ├── Task.swift │ └── Utils │ │ ├── PayloadAction.swift │ │ └── RxSwift │ │ ├── ObservableType+Extensions.swift │ │ └── PrimitiveSequenceType+Extensions.swift └── TestMiddleware │ └── TestMiddleware.swift ├── Templates └── Autocopy.stencil ├── Tests ├── LinuxMain.swift └── MiniSwiftTests │ ├── ActionTests.swift │ ├── ChainTests.swift │ ├── Common.swift │ ├── DispatcherTests.swift │ ├── PromiseTests.swift │ ├── ReducerTests.swift │ ├── RxTests │ ├── ObservableTypeTests.swift │ └── PrimitiveSequenceTypeTests.swift │ ├── TaskTests.swift │ ├── Utils │ └── Foundation │ │ ├── Dictionary+ExtensionsTests.swift │ │ └── DispatchQueueTests.swift │ └── XCTestManifests.swift ├── _config.yml ├── bin ├── pre-commit.rb └── pre-push.rb ├── docs ├── Mini.swift ├── Mini │ ├── README.md │ ├── classes │ │ ├── Dispatcher.md │ │ ├── DispatcherSubscription.md │ │ ├── ForwardingChain.md │ │ ├── OrderedSet.md │ │ ├── Reducer.md │ │ ├── ReducerGroup.md │ │ ├── RootChain.md │ │ ├── SharedDictionary.md │ │ └── Store.md │ ├── enums │ │ └── UI.md │ ├── extensions │ │ ├── Action.md │ │ ├── Dictionary.md │ │ ├── ObservableType.md │ │ ├── Optional.md │ │ ├── StateType.md │ │ ├── Store.md │ │ └── StoreType.md │ ├── methods │ │ └── ^(_:).md │ ├── protocols │ │ ├── Action.md │ │ ├── Chain.md │ │ ├── Group.md │ │ ├── Middleware.md │ │ ├── OptionalType.md │ │ ├── Service.md │ │ ├── StateType.md │ │ └── StoreType.md │ └── structs │ │ └── DispatchMode.md ├── MiniPromises.swift ├── MiniPromises │ ├── README.md │ ├── classes │ │ └── Promise.md │ ├── enums │ │ ├── Lifetime.md │ │ └── Promises.md │ ├── extensions │ │ ├── Dictionary.md │ │ ├── EmptyAction.md │ │ ├── ObservableType.md │ │ ├── PrimitiveSequenceType.md │ │ └── Promise.md │ └── protocols │ │ ├── CompletableAction.md │ │ ├── EmptyAction.md │ │ ├── KeyedCompletableAction.md │ │ ├── KeyedPayloadAction.md │ │ ├── PayloadAction.md │ │ └── PromiseType.md ├── MiniTasks.swift ├── MiniTasks │ ├── README.md │ ├── classes │ │ └── TypedTask.md │ ├── enums │ │ └── Status.md │ ├── extensions │ │ ├── EmptyAction.md │ │ ├── ObservableType.md │ │ └── PrimitiveSequenceType.md │ └── protocols │ │ ├── CompletableAction.md │ │ ├── EmptyAction.md │ │ ├── KeyedCompletableAction.md │ │ ├── KeyedPayloadAction.md │ │ └── PayloadAction.md └── README.md ├── fastlane ├── Fastfile ├── Pluginfile └── README.md └── info.plist /.bundle/config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_PATH: ".gems" -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | 2 | coverage: 3 | ignore: 4 | - Templates 5 | - Scripts 6 | - Tests 7 | - docs -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | * @hyperdevs-team/ios-owners 5 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### PR's key points 2 | … 3 | 4 | ### How to review this PR? 5 | … 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gems 2 | 3 | # Created by https://www.gitignore.io/api/swift,xcode,carthage 4 | 5 | ### Swift ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xcuserstate 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | *.dSYM.zip 33 | *.dSYM 34 | 35 | ## Playgrounds 36 | timeline.xctimeline 37 | playground.xcworkspace 38 | 39 | # Swift Package Manager 40 | # 41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 42 | # Packages/ 43 | .build/ 44 | 45 | # CocoaPods 46 | # 47 | # We recommend against adding the Pods directory to your .gitignore. However 48 | # you should judge for yourself, the pros and cons are mentioned at: 49 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 50 | # 51 | # Pods/ 52 | 53 | # Carthage 54 | # 55 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 56 | 57 | Carthage/ 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | 71 | 72 | ### Xcode ### 73 | # Xcode 74 | # 75 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 76 | 77 | ## Build generated 78 | build/ 79 | DerivedData/ 80 | 81 | ## Various settings 82 | *.pbxuser 83 | !default.pbxuser 84 | *.mode1v3 85 | !default.mode1v3 86 | *.mode2v3 87 | !default.mode2v3 88 | *.perspectivev3 89 | !default.perspectivev3 90 | xcuserdata/ 91 | 92 | ## Other 93 | *.moved-aside 94 | *.xccheckout 95 | *.xcscmblueprint 96 | 97 | 98 | ### Carthage ### 99 | # Carthage - A simple, decentralized dependency manager for Cocoa 100 | Carthage/Checkouts/ 101 | Carthage/Build/ 102 | 103 | .DS_Store 104 | 105 | Scripts/data/request.yml 106 | Scripts/tmp/* 107 | !Scripts/tmp/.gitkeep 108 | .swiftpm/* 109 | Logs/* 110 | ModuleCache.noindex/* 111 | Index/* 112 | 113 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | swiftlint: 2 | config_file: .swiftlint.yml 3 | ruby: 4 | enabled: false -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - todo 3 | - conditional_returns_on_newline 4 | - unused_optional_binding 5 | - file_header 6 | opt_in_rules: 7 | #lint 8 | - prohibited_super_call 9 | - overridden_super_call 10 | - yoda_condition 11 | #style 12 | - unneeded_parentheses_in_closure_argument 13 | - operator_usage_whitespace 14 | - number_separator 15 | - closure_spacing 16 | - closure_end_indentation 17 | - attributes 18 | - file_header 19 | #idiomatic 20 | - redundant_nil_coalescing 21 | - nimble_operator 22 | - explicit_init 23 | #performance 24 | - empty_count 25 | - contains_over_first_not_nil 26 | - empty_string 27 | - first_where 28 | - sorted_first_last 29 | included: 30 | - Sources 31 | excluded: 32 | - docs 33 | - Scripts 34 | - Tests 35 | - Package.swift 36 | closure_end_indentation: 37 | severity: error 38 | force_cast: 39 | severity: error 40 | force_try: 41 | severity: error 42 | function_body_length: 43 | warning: 60 44 | error: 100 45 | cyclomatic_complexity: 46 | warning: 60 47 | error: 60 48 | type_body_length: 49 | warning: 400 50 | error: 500 51 | line_length: 52 | warning: 250 53 | error: 400 54 | file_length: 55 | warning: 700 56 | error: 1200 57 | type_name: 58 | min_length: 3 59 | max_length: 40 60 | severity: error 61 | identifier_name: 62 | min_length: 3 63 | max_length: 40 64 | severity: error 65 | excluded: 66 | - id 67 | - i 68 | attributes: 69 | always_on_same_line: 70 | - "@IBOutlet" 71 | - "@IBAction" 72 | - "@NSManaged" 73 | - "@testable" 74 | reporter: "xcode" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | if: tag is blank 3 | 4 | cache: 5 | bundler: true 6 | cocoapods: true 7 | directories: 8 | - $TRAVIS_BUILD_DIR/target 9 | # https://stackoverflow.com/questions/39930171/cache-brew-builds-with-travis-ci 10 | - $HOME/Library/Caches/Homebrew 11 | - /usr/local/Homebrew/ 12 | # used in OSX custom build script dealing with local bottle caching 13 | - $HOME/local_bottle_metadata 14 | addons: 15 | homebrew: 16 | brewfile: true 17 | update: true 18 | 19 | before_install: | 20 | if [ -n "$IS_OSX" ]; then 21 | TAPS="$(brew --repository)/Library/Taps" 22 | if [ -e "$TAPS/caskroom/homebrew-cask" -a -e "$TAPS/homebrew/homebrew-cask" ]; then 23 | rm -rf "$TAPS/caskroom/homebrew-cask" 24 | fi 25 | find "$TAPS" -type d -name .git -exec \ 26 | bash -xec ' 27 | cd $(dirname '\''{}'\'') || echo "status: $?" 28 | git clean -fxd || echo "status: $?" 29 | sleep 1 || echo "status: $?" 30 | git status || echo "status: $?"' \; || echo "status: $?" 31 | brew_cache_cleanup 32 | fi 33 | before_cache: | 34 | # Cleanup dirs to be cached 35 | set -e; set -x 36 | if [ -n "$IS_OSX" ]; then 37 | # When Taps is cached, this dir causes "Error: file exists" on `brew update` 38 | if [ -e "$(brew --repository)/Library/Taps/homebrew/homebrew-cask/homebrew-cask" ]; then 39 | rm -rf "$(brew --repository)/Library/Taps/homebrew/homebrew-cask/homebrew-cask" 40 | fi 41 | brew_cache_cleanup 42 | fi 43 | set +x; set +e 44 | 45 | jobs: 46 | include: 47 | - stage: Continuous Integration Coverage 48 | name: SwiftPM macOS 49 | os: osx 50 | osx_image: xcode11.3 51 | before_script: 52 | - bundle install 53 | script: 54 | - set -o pipefail 55 | - rake 56 | - bundle exec fastlane pass_tests 57 | - bundle exec pod repo update 58 | - rake pods 59 | - bundle exec danger 60 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew "swiftlint" 2 | tap "minuscorp/moduleinterface" 3 | brew "moduleinterface" -------------------------------------------------------------------------------- /Brewfile.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "entries": { 3 | "brew": { 4 | "swiftlint": { 5 | "version": "0.39.1", 6 | "bottle": { 7 | "cellar": ":any_skip_relocation", 8 | "prefix": "/usr/local", 9 | "files": { 10 | "catalina": { 11 | "url": "https://homebrew.bintray.com/bottles/swiftlint-0.39.1.catalina.bottle.tar.gz", 12 | "sha256": "5c51f9a46a0ab28229460afd1037825bf1bef6b577113303057602487a898553" 13 | }, 14 | "mojave": { 15 | "url": "https://homebrew.bintray.com/bottles/swiftlint-0.39.1.mojave.bottle.tar.gz", 16 | "sha256": "408d3baeeb687da9cb753abf154835d71672eb29f1288538e5fa576f6d8880d1" 17 | } 18 | } 19 | } 20 | }, 21 | "moduleinterface": { 22 | "version": "0.0.4", 23 | "bottle": false 24 | } 25 | }, 26 | "tap": { 27 | "minuscorp/moduleinterface": { 28 | "revision": "aee0fab5633b511622b99eb6fc45a8ca9a27acd8" 29 | } 30 | } 31 | }, 32 | "system": { 33 | "macos": { 34 | "catalina": { 35 | "HOMEBREW_VERSION": "2.2.7-20-g10ba0d5", 36 | "HOMEBREW_PREFIX": "/usr/local", 37 | "Homebrew/homebrew-core": "33cacce7a4cff82d1015f591838792216d170d1f", 38 | "CLT": "11.0.28.3", 39 | "Xcode": "11.3.1", 40 | "macOS": "10.15.2" 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-swift/658eb0370064a351820bb1ba26dd9b675d23d012/CHANGELOG.md -------------------------------------------------------------------------------- /DangerFile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # FILE HELPERS 4 | gitfiles = (git.modified_files + git.added_files).uniq 5 | has_code_changes = !gitfiles.grep(/^Sources/).empty? 6 | has_tests_changes = !gitfiles.grep(/^Tests/).empty? 7 | 8 | # bq_helpers.scan_files 9 | 10 | # BASIC CHECKS: 11 | warn 'Big PR, try to keep changes smaller if you can 😜' if git.lines_of_code > 500 12 | 13 | # BUILD PARSE: 14 | # bq_helpers.build_reports.each do |path| 15 | # path = Pathname(path) 16 | # xcode_summary.report path.to_s 17 | # end 18 | # 19 | # #JUNIT PARSE: 20 | # bq_helpers.test_reports.each do |path| 21 | # path = Pathname(path) 22 | # junit.parse path.to_s 23 | # junit.report 24 | # 25 | # all_test = junit.tests.map(&:attributes) 26 | # slowest_test = all_test.sort_by { |attributes| attributes[:time].to_f }.last 27 | # message "⌛️ **[#{bq_helpers.read_platform_from_file(path: path)}]** Slowest test: #{slowest_test[:name]} took #{'%.3f' % slowest_test[:time]} seconds" 28 | # end 29 | 30 | # SWIFTLINT 31 | swiftlint.config_file = '.swiftlint.yml' 32 | swiftlint.lint_files fail_on_error: true 33 | swiftlint.max_num_violations = 20 34 | swiftlint.lint_files 35 | swiftlint.directory = 'Sources' 36 | 37 | # TEST EVOLUTION CHECK: 38 | if has_code_changes 39 | warn('You have changes in code but there is no changes in any test... do you sleep well at night? 🤨') unless has_tests_changes 40 | end 41 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem "trainer" 5 | gem "overcommit" 6 | gem "xcpretty-json-formatter" 7 | gem "cocoapods" 8 | gem "danger" 9 | gem "danger-xcodebuild" 10 | gem "danger-swiftlint" 11 | gem "danger-xcov" 12 | gem "danger-junit" 13 | gem "danger-xcode_summary" 14 | gem "danger-bq_helpers", git: "https://github.com/bq/danger-bq_helpers" 15 | 16 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 17 | eval_gemfile('./fastlane/Pluginfile') if File.exist?(plugins_path) 18 | -------------------------------------------------------------------------------- /Mini-Swift.podspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Pod::Spec.new do |s| 4 | s.name = 'Mini-Swift' 5 | s.version = '3.0.4' 6 | s.swift_version = '5.0' 7 | s.summary = 'The minimal expression of a Flux architecture in Swift.' 8 | 9 | s.description = <<~DESC 10 | The minimal expression of a Flux architecture in Swift. 11 | 12 | Mini is built with be a first class citizen in Swift applications: macOS, iOS and tvOS applications. 13 | With Mini, you can create a thread-safe application with a predictable unidirectional data flow, 14 | focusing on what really matters: build awesome applications. 15 | DESC 16 | 17 | s.homepage = 'https://github.com/bq/Mini-Swift' 18 | s.license = { type: 'APACHE', file: 'LICENSE' } 19 | s.author = { 'bq' => 'info@bq.com' } 20 | s.source = { git: 'https://github.com/bq/mini-swift.git', tag: "v#{s.version}" } 21 | s.social_media_url = 'https://twitter.com/bqreaders' 22 | 23 | s.ios.deployment_target = '11.0' 24 | 25 | s.osx.deployment_target = '10.13' 26 | 27 | s.tvos.deployment_target = '11.0' 28 | 29 | s.frameworks = 'Foundation' 30 | 31 | s.dependency('RxSwift', '~> 5') 32 | s.dependency('SwiftNIOConcurrencyHelpers', '~> 2.10.0') 33 | 34 | s.default_subspec = 'Core' 35 | 36 | s.module_name = 'Mini' 37 | 38 | s.subspec('Core') do |ss| 39 | ss.ios.source_files = 'Sources/Mini/**/*.swift' 40 | 41 | ss.osx.source_files = 'Sources/Mini/**/*.swift' 42 | 43 | ss.tvos.source_files = 'Sources/Mini/**/*.swift' 44 | end 45 | 46 | s.subspec('Log') do |ss| 47 | ss.ios.dependency('Mini-Swift/Core') 48 | ss.ios.source_files = 'Sources/LoggingService/*.swift' 49 | 50 | ss.osx.dependency('Mini-Swift/Core') 51 | ss.osx.source_files = 'Sources/LoggingService/*.swift' 52 | 53 | ss.tvos.dependency('Mini-Swift/Core') 54 | ss.tvos.source_files = 'Sources/LoggingService/*.swift' 55 | end 56 | 57 | s.subspec('Test') do |ss| 58 | ss.ios.dependency('Mini-Swift/Core') 59 | ss.ios.source_files = 'Sources/TestMiddleware/*.swift' 60 | 61 | ss.osx.dependency('Mini-Swift/Core') 62 | ss.osx.source_files = 'Sources/TestMiddleware/*.swift' 63 | 64 | ss.tvos.dependency('Mini-Swift/Core') 65 | ss.tvos.source_files = 'Sources/TestMiddleware/*.swift' 66 | end 67 | 68 | s.subspec('MiniTasks') do |ss| 69 | ss.ios.dependency('Mini-Swift/Core') 70 | ss.ios.source_files = 'Sources/MiniTasks/*.swift' 71 | 72 | ss.osx.dependency('Mini-Swift/Core') 73 | ss.osx.source_files = 'Sources/MiniTasks/*.swift' 74 | 75 | ss.tvos.dependency('Mini-Swift/Core') 76 | ss.tvos.source_files = 'Sources/MiniTasks/*.swift' 77 | end 78 | 79 | s.subspec('MiniPromises') do |ss| 80 | ss.ios.dependency('Mini-Swift/Core') 81 | ss.ios.source_files = 'Sources/MiniPromises/*.swift' 82 | 83 | ss.osx.dependency('Mini-Swift/Core') 84 | ss.osx.source_files = 'Sources/MiniPromises/*.swift' 85 | 86 | ss.tvos.dependency('Mini-Swift/Core') 87 | ss.tvos.source_files = 'Sources/MiniPromises/*.swift' 88 | end 89 | 90 | s.preserve_paths = ['Templates/*.stencil'] 91 | end 92 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "object": { 3 | "pins": [ 4 | { 5 | "package": "Commandant", 6 | "repositoryURL": "https://github.com/Carthage/Commandant.git", 7 | "state": { 8 | "branch": null, 9 | "revision": "ab68611013dec67413628ac87c1f29e8427bc8e4", 10 | "version": "0.17.0" 11 | } 12 | }, 13 | { 14 | "package": "Curry", 15 | "repositoryURL": "https://github.com/thoughtbot/Curry.git", 16 | "state": { 17 | "branch": null, 18 | "revision": "4331dd50bc1db007db664a23f32e6f3df93d4e1a", 19 | "version": "4.0.2" 20 | } 21 | }, 22 | { 23 | "package": "CwlCatchException", 24 | "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", 25 | "state": { 26 | "branch": null, 27 | "revision": "f809deb30dc5c9d9b78c872e553261a61177721a", 28 | "version": "2.0.0" 29 | } 30 | }, 31 | { 32 | "package": "CwlPreconditionTesting", 33 | "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting", 34 | "state": { 35 | "branch": "master", 36 | "revision": "02b7a39a99c4da27abe03cab2053a9034379639f", 37 | "version": null 38 | } 39 | }, 40 | { 41 | "package": "Komondor", 42 | "repositoryURL": "https://github.com/shibapm/Komondor.git", 43 | "state": { 44 | "branch": null, 45 | "revision": "3cd6d76887816ead5931ddbfb249c2935f518e17", 46 | "version": "1.0.4" 47 | } 48 | }, 49 | { 50 | "package": "Logger", 51 | "repositoryURL": "https://github.com/f-meloni/Logger", 52 | "state": { 53 | "branch": null, 54 | "revision": "53c3ecca5abe8cf46697e33901ee774236d94cce", 55 | "version": "0.2.3" 56 | } 57 | }, 58 | { 59 | "package": "MarkdownGenerator", 60 | "repositoryURL": "https://github.com/eneko/MarkdownGenerator.git", 61 | "state": { 62 | "branch": null, 63 | "revision": "1fc31be6b66245187c3f87f35d86b6aeac6dfe7f", 64 | "version": "0.5.0" 65 | } 66 | }, 67 | { 68 | "package": "ModuleInterface", 69 | "repositoryURL": "https://github.com/minuscorp/ModuleInterface", 70 | "state": { 71 | "branch": null, 72 | "revision": "abae15b50bad512a77c1dfea3f205731d3542a19", 73 | "version": "0.0.4" 74 | } 75 | }, 76 | { 77 | "package": "Nimble", 78 | "repositoryURL": "https://github.com/Quick/Nimble", 79 | "state": { 80 | "branch": "master", 81 | "revision": "eea5843b34beb559dd51cf004953f75028e47add", 82 | "version": null 83 | } 84 | }, 85 | { 86 | "package": "PackageConfig", 87 | "repositoryURL": "https://github.com/shibapm/PackageConfig.git", 88 | "state": { 89 | "branch": null, 90 | "revision": "fd0829aac9851434b3d2db0890e27bc489fc973a", 91 | "version": "0.12.2" 92 | } 93 | }, 94 | { 95 | "package": "Quick", 96 | "repositoryURL": "https://github.com/Quick/Quick.git", 97 | "state": { 98 | "branch": null, 99 | "revision": "33682c2f6230c60614861dfc61df267e11a1602f", 100 | "version": "2.2.0" 101 | } 102 | }, 103 | { 104 | "package": "Rainbow", 105 | "repositoryURL": "https://github.com/onevcat/Rainbow", 106 | "state": { 107 | "branch": null, 108 | "revision": "9c52c1952e9b2305d4507cf473392ac2d7c9b155", 109 | "version": "3.1.5" 110 | } 111 | }, 112 | { 113 | "package": "Rocket", 114 | "repositoryURL": "https://github.com/shibapm/Rocket", 115 | "state": { 116 | "branch": null, 117 | "revision": "f75c9736733b489a3456b4f3a47cf13adb99f197", 118 | "version": "0.9.2" 119 | } 120 | }, 121 | { 122 | "package": "RxOptional", 123 | "repositoryURL": "https://github.com/RxSwiftCommunity/RxOptional", 124 | "state": { 125 | "branch": null, 126 | "revision": "98a1895918e9ba9735a15207dd9c7120e9f51843", 127 | "version": "4.1.0" 128 | } 129 | }, 130 | { 131 | "package": "RxSwift", 132 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git", 133 | "state": { 134 | "branch": null, 135 | "revision": "b3e888b4972d9bc76495dd74d30a8c7fad4b9395", 136 | "version": "5.0.1" 137 | } 138 | }, 139 | { 140 | "package": "ShellOut", 141 | "repositoryURL": "https://github.com/JohnSundell/ShellOut.git", 142 | "state": { 143 | "branch": null, 144 | "revision": "4ebf25863deb9c3c02696704fc3d39736183f258", 145 | "version": "2.2.1" 146 | } 147 | }, 148 | { 149 | "package": "SourceDocs", 150 | "repositoryURL": "https://github.com/eneko/SourceDocs", 151 | "state": { 152 | "branch": null, 153 | "revision": "dfa6fdae84c555264ad9795dcb7d629339191c01", 154 | "version": "0.6.1" 155 | } 156 | }, 157 | { 158 | "package": "SourceKitten", 159 | "repositoryURL": "https://github.com/jpsim/SourceKitten", 160 | "state": { 161 | "branch": null, 162 | "revision": "356551fc513eb12ed779bb369f79cf86a3a01599", 163 | "version": "0.27.0" 164 | } 165 | }, 166 | { 167 | "package": "llbuild", 168 | "repositoryURL": "https://github.com/apple/swift-llbuild.git", 169 | "state": { 170 | "branch": null, 171 | "revision": "f1c9ad9a253cdf1aa89a7f5c99c30b4513b06ddb", 172 | "version": "0.1.1" 173 | } 174 | }, 175 | { 176 | "package": "swift-nio", 177 | "repositoryURL": "https://github.com/apple/swift-nio.git", 178 | "state": { 179 | "branch": null, 180 | "revision": "32760eae40e6b7cb81d4d543bb0a9f548356d9a2", 181 | "version": "2.7.1" 182 | } 183 | }, 184 | { 185 | "package": "SwiftPM", 186 | "repositoryURL": "https://github.com/apple/swift-package-manager.git", 187 | "state": { 188 | "branch": null, 189 | "revision": "8656a25cb906c1896339f950ac960ee1b4fe8034", 190 | "version": "0.4.0" 191 | } 192 | }, 193 | { 194 | "package": "SwiftFormat", 195 | "repositoryURL": "https://github.com/nicklockwood/SwiftFormat", 196 | "state": { 197 | "branch": null, 198 | "revision": "b564570c139d1c36292e8a5bb08d4ba6818b0a91", 199 | "version": "0.43.0" 200 | } 201 | }, 202 | { 203 | "package": "SwiftLint", 204 | "repositoryURL": "https://github.com/Realm/SwiftLint", 205 | "state": { 206 | "branch": null, 207 | "revision": "8dc3e811934704a3d0e46a8ffd1de785fddcf7ec", 208 | "version": "0.38.0" 209 | } 210 | }, 211 | { 212 | "package": "SwiftShell", 213 | "repositoryURL": "https://github.com/kareman/SwiftShell", 214 | "state": { 215 | "branch": null, 216 | "revision": "beebe43c986d89ea5359ac3adcb42dac94e5e08a", 217 | "version": "4.1.2" 218 | } 219 | }, 220 | { 221 | "package": "SwiftyTextTable", 222 | "repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git", 223 | "state": { 224 | "branch": null, 225 | "revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3", 226 | "version": "0.9.0" 227 | } 228 | }, 229 | { 230 | "package": "SWXMLHash", 231 | "repositoryURL": "https://github.com/drmohundro/SWXMLHash.git", 232 | "state": { 233 | "branch": null, 234 | "revision": "a4931e5c3bafbedeb1601d3bb76bbe835c6d475a", 235 | "version": "5.0.1" 236 | } 237 | }, 238 | { 239 | "package": "System", 240 | "repositoryURL": "https://github.com/eneko/System.git", 241 | "state": { 242 | "branch": null, 243 | "revision": "381352d9479fb9c75d99a5ae5503f7d560c62449", 244 | "version": "0.2.0" 245 | } 246 | }, 247 | { 248 | "package": "Yams", 249 | "repositoryURL": "https://github.com/jpsim/Yams.git", 250 | "state": { 251 | "branch": null, 252 | "revision": "c947a306d2e80ecb2c0859047b35c73b8e1ca27f", 253 | "version": "2.0.0" 254 | } 255 | } 256 | ] 257 | }, 258 | "version": 1 259 | } 260 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Mini", 8 | platforms: [ 9 | .iOS(.v11), 10 | .macOS(.v10_13), 11 | .tvOS(.v11), 12 | ], 13 | products: [ 14 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 15 | .library( 16 | name: "Mini", 17 | targets: ["Mini"] 18 | ), 19 | .library( 20 | name: "Mini/Log", 21 | targets: ["Mini", "LoggingService"] 22 | ), 23 | .library( 24 | name: "Mini/Test", 25 | targets: ["Mini", "TestMiddleware"] 26 | ), 27 | .library( 28 | name: "MiniTask", 29 | targets: ["Mini", "MiniTasks"] 30 | ), 31 | .library( 32 | name: "MiniPromise", 33 | targets: ["Mini", "MiniPromises"] 34 | ), 35 | ], 36 | dependencies: [ 37 | // Dependencies declare other packages that this package depends on. 38 | .package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "5.0.0")), 39 | .package(url: "https://github.com/RxSwiftCommunity/RxOptional", .upToNextMajor(from: "4.1.0")), 40 | .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.7.1")), 41 | // Development 42 | .package(url: "https://github.com/Quick/Nimble", .branch("master")), // dev 43 | .package(url: "https://github.com/mattgallagher/CwlPreconditionTesting", .branch("master")), // dev 44 | .package(url: "https://github.com/minuscorp/ModuleInterface", from: "0.0.1"), // dev 45 | .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.35.8"), // dev 46 | .package(url: "https://github.com/jpsim/SourceKitten", from: "0.26.0"), // dev 47 | .package(url: "https://github.com/shibapm/Rocket", from: "0.4.0"), // dev 48 | .package(url: "https://github.com/Realm/SwiftLint", from: "0.35.0"), // dev 49 | .package(url: "https://github.com/eneko/SourceDocs", from: "0.6.1"), // dev 50 | .package(url: "https://github.com/shibapm/PackageConfig.git", from: "0.12.2"), // dev 51 | .package(url: "https://github.com/shibapm/Komondor.git", from: "1.0.0"), // dev 52 | .package(url: "https://github.com/Carthage/Commandant.git", .exact("0.17.0")), // dev 53 | ], 54 | targets: [ 55 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 56 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 57 | .target( 58 | name: "Mini", 59 | dependencies: ["RxSwift", "NIOConcurrencyHelpers", "RxOptional"] 60 | ), 61 | .target( 62 | name: "LoggingService", 63 | dependencies: ["Mini"] 64 | ), 65 | .target( 66 | name: "TestMiddleware", 67 | dependencies: ["Mini"] 68 | ), 69 | .target( 70 | name: "MiniTasks", 71 | dependencies: ["Mini"] 72 | ), 73 | .target( 74 | name: "MiniPromises", 75 | dependencies: ["Mini"] 76 | ), 77 | .testTarget(name: "MiniSwiftTests", dependencies: ["Mini", "MiniTasks", "MiniPromises", "TestMiddleware", "NIOConcurrencyHelpers", "RxSwift", "Nimble", "RxTest", "RxBlocking", "RxOptional"]), // dev 78 | ], 79 | swiftLanguageVersions: [.version("5")] 80 | ) 81 | 82 | #if canImport(PackageConfig) 83 | import PackageConfig 84 | 85 | let config = PackageConfiguration([ 86 | "rocket": [ 87 | "before": [ 88 | "brew bundle", 89 | "bundle install", 90 | "bundle exec fastlane run version_bump_podspec version_number:`echo $VERSION | cut -d \"v\" -f 2`", 91 | "rake docs", 92 | ], 93 | "after": [ 94 | "pod lib lint --allow-warnings", 95 | "pod trunk push --allow-warnings", 96 | ], 97 | ], 98 | "komondor": [ 99 | "pre-push": "swift test", 100 | "pre-commit": [ 101 | "swift test", 102 | "swift run swiftlint autocorrect --path Sources/", 103 | "swift run swiftlint autocorrect --path Tests/", 104 | "swift test --generate-linuxmain", 105 | "swift run swiftformat .", 106 | "swift run swiftlint autocorrect --path Sources/", 107 | "git add .", 108 | ], 109 | ], 110 | ]).write() 111 | #endif 112 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | task default: %w[setup] 4 | 5 | task(:setup) do 6 | 7 | raise '`brew` is required. Please install brew. https://brew.sh/' unless system('which brew') 8 | 9 | puts('➡️ Bundle') 10 | sh('brew bundle') 11 | sh('bundle install') 12 | 13 | puts('➡️ SPM Resolve Dependencies') 14 | sh('swift package resolve') 15 | 16 | puts('➡️ Installing git hooks') 17 | sh('swift run komondor install') 18 | end 19 | 20 | task(:build) do 21 | sh("swift build --disable-sandbox -c release") 22 | end 23 | 24 | task(:test) do 25 | sh('swift test') 26 | end 27 | 28 | task(:pods) do 29 | sh('bundle exec pod lib lint --allow-warnings --fail-fast --subspec="Core"') 30 | sh('bundle exec pod lib lint --allow-warnings --fail-fast --subspec="Log"') 31 | sh('bundle exec pod lib lint --allow-warnings --fail-fast --subspec="Test"') 32 | sh('bundle exec pod lib lint --allow-warnings --fail-fast --subspec="MiniPromises"') 33 | sh('bundle exec pod lib lint --allow-warnings --fail-fast --subspec="MiniTasks"') 34 | end 35 | 36 | task(:docs) do 37 | sh('sourcedocs generate --min-acl public --spm-module Mini --output-folder docs/Mini') 38 | sh('sourcedocs generate --min-acl public --spm-module MiniTasks --output-folder docs/MiniTasks') 39 | sh('sourcedocs generate --min-acl public --spm-module MiniPromises --output-folder docs/MiniPromises') 40 | sh('moduleinterface generate --spm-module Mini --output-folder docs') 41 | sh('moduleinterface generate --spm-module MiniTasks --output-folder docs') 42 | sh('moduleinterface generate --spm-module MiniPromises --output-folder docs') 43 | end 44 | -------------------------------------------------------------------------------- /Scripts/data/request.template.yml: -------------------------------------------------------------------------------- 1 | { 2 | key.request: source.request.editor.open.interface, 3 | key.name: "20D5E7C4-FDCE-448A-97EA-D548CEA1CCFE", 4 | key.compilerargs: [ 5 | "-target", 6 | "x86_64-apple-macos10.10", 7 | "-module-name", 8 | "MiniSwiftTests", 9 | "-sdk", 10 | "/Applications/Xcode-11.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 11 | "-I", 12 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Products/Debug", 13 | "-I", 14 | "/Applications/Xcode-11.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib", 15 | "-I", 16 | "/Applications/Xcode-11.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/usr/local/include", 17 | "-F", 18 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Products/Debug", 19 | "-F", 20 | "/Applications/Xcode-11.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks", 21 | "-F", 22 | "/Applications/Xcode-11.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/PrivateFrameworks", 23 | "-D", 24 | "SWIFT_PACKAGE", 25 | "-D", 26 | "DEBUG", 27 | "-D", 28 | "Xcode", 29 | "-Xcc", 30 | "-I", 31 | "-Xcc", 32 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Products/Debug", 33 | "-Xcc", 34 | "-I", 35 | "-Xcc", 36 | "/Applications/Xcode-11.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib", 37 | "-Xcc", 38 | "-I", 39 | "-Xcc", 40 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/SourcePackages/checkouts/swift-nio/Sources/CNIOAtomics/include", 41 | "-Xcc", 42 | "-I", 43 | "-Xcc", 44 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Products/Debug/include", 45 | "-Xcc", 46 | "-I", 47 | "-Xcc", 48 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Intermediates.noindex/Mini.build/Debug/MiniSwiftTests.build/DerivedSources-normal/x86_64", 49 | "-Xcc", 50 | "-I", 51 | "-Xcc", 52 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Intermediates.noindex/Mini.build/Debug/MiniSwiftTests.build/DerivedSources/x86_64", 53 | "-Xcc", 54 | "-I", 55 | "-Xcc", 56 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Intermediates.noindex/Mini.build/Debug/MiniSwiftTests.build/DerivedSources", 57 | "-Xcc", 58 | "-F", 59 | "-Xcc", 60 | "/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Products/Debug", 61 | "-Xcc", 62 | "-F", 63 | "-Xcc", 64 | "/Applications/Xcode-11.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks", 65 | "-Xcc", 66 | "-D", 67 | "-Xcc", 68 | "DEBUG=1", 69 | "-Xcc", 70 | "-fmodule-map-file=/Users/minuscorp/Library/Developer/Xcode/DerivedData/mini-swift-faaeagcnetprhtagxvykrniwnkap/Build/Intermediates.noindex/GeneratedModuleMaps/macosx/CNIOAtomics.modulemap", 71 | "" 72 | ], 73 | key.modulename: "Mini", 74 | key.toolchains: [ 75 | "com.apple.dt.toolchain.XcodeDefault" 76 | ], 77 | key.synthesizedextensions: 1 78 | } -------------------------------------------------------------------------------- /Scripts/update_changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed "s/## Master/\\$(printf '## Master\\\n\\\n## '"$VERSION")/" CHANGELOG.md > tmpCHANGELOG.md 4 | mv -f tmpCHANGELOG.md CHANGELOG.md -------------------------------------------------------------------------------- /Scripts/update_docs.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | require 'yaml' 5 | 6 | `swift package generate-xcodeproj` 7 | `xcodebuild -scheme Mini-Package -configuration Debug -derivedDataPath '#{Dir.pwd}' clean build` 8 | 9 | # Derive the lib dirs from via xcode 10 | debug_dir = `xcodebuild -project ./Mini.xcodeproj -showBuildSettings -configuration Debug | grep -m 1 "CONFIGURATION_BUILD_DIR" | grep -oEi "\/.*"`.strip 11 | sdk_dir = `xcodebuild -project ./Mini.xcodeproj -showBuildSettings -configuration Debug | grep -m 1 "SDKROOT" | grep -oEi "\/.*"`.strip 12 | tmp_dir = `xcodebuild -project ./Mini.xcodeproj -showBuildSettings -configuration Debug | grep -m 1 "PROJECT_TEMP_ROOT" | grep -oEi "\/.*"`.strip 13 | 14 | # Apply them to the template to make a request to SourceKit 15 | # See: https://github.com/jpsim/SourceKitten/issues/405 16 | # 17 | template = File.read("Scripts/data/request.template.yml") 18 | file = template 19 | .gsub("[DEBUG_ROOT]", debug_dir) 20 | .gsub("[SDK_ROOT]", sdk_dir) 21 | .gsub("[CWD]", Dir.pwd) 22 | .gsub("[TMP_DIR]", tmp_dir) 23 | 24 | File.write("Scripts/data/request.yml", file) 25 | 26 | # Run it, get JSON back 27 | response = `swift run sourcekitten request --yaml Scripts/data/request.yml` 28 | 29 | interface = JSON.parse(response) 30 | 31 | # Write just the interface all to a file, in theory we can make a HTML version of this in the future 32 | File.write("docs/Mini.swift", interface["key.sourcetext"]) 33 | 34 | # Format the updated file 35 | `swift run swiftformat docs/Mini.swift` 36 | 37 | `rm -rf Mini.xcodeproj` 38 | -------------------------------------------------------------------------------- /Sources/LoggingService/LoggingService.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | #if canImport(Mini) 19 | import Mini 20 | 21 | public class LoggingService: Service { 22 | public var id: UUID = UUID() 23 | 24 | public var perform: ServiceChain { 25 | return { action, _ -> Void in 26 | NSLog(String(dumping: action)) 27 | } 28 | } 29 | 30 | public init() {} 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /Sources/LoggingService/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension String { 20 | init(dumping object: T) { 21 | self.init() 22 | dump(object, to: &self) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Mini/Action.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /** 20 | Protocol that has to be conformed by any object that can be dispatched 21 | by a `Dispatcher` object. 22 | */ 23 | public protocol Action {} 24 | 25 | extension Action { 26 | /// String used as tag of the given Action based on his name. 27 | /// - Returns: The name of the action as a String. 28 | public var innerTag: String { 29 | return String(describing: type(of: self)) 30 | } 31 | 32 | /** 33 | Static method to retrieve the name of the action as a tag.action. 34 | 35 | Calling this method in a static way return the Action name .Type cause it's not an instance.Action 36 | For this reason the String is split in two separated by a dot and returning the first part. 37 | */ 38 | static var tag: String { 39 | let tag = String(describing: type(of: self)) 40 | return tag.components(separatedBy: ".")[0] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/Mini/ActionReducer.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | /** 21 | The `Reducer` defines the behavior to be executed when a certain 22 | `Action` object is received. 23 | */ 24 | public class Reducer: Disposable { 25 | /// The `Action` type which the `Reducer` listens to. 26 | public let action: A.Type 27 | /// The `Dispatcher` object that sends the `Action` objects. 28 | public let dispatcher: Dispatcher 29 | /// The behavior to be executed when the `Dispatcher` sends a certain `Action` 30 | public let reducer: (A) -> Void 31 | 32 | private var disposable: Disposable! 33 | 34 | /** 35 | Initializes a new `Reducer` object. 36 | - Parameter action: The `Action` type that will be listened to. 37 | - Parameter dispatcher: The `Dispatcher` that sends the `Action`. 38 | - Parameter reducer: The closure that will be executed when the `Dispatcher` 39 | sends the defined `Action` type. 40 | */ 41 | public init(of action: A.Type, on dispatcher: Dispatcher, reducer: @escaping (A) -> Void) { 42 | self.action = action 43 | self.dispatcher = dispatcher 44 | self.reducer = reducer 45 | disposable = build() 46 | } 47 | 48 | private func build() -> Disposable { 49 | let disposable = dispatcher.subscribe(tag: action.tag) { 50 | self.reducer($0) 51 | } 52 | return disposable 53 | } 54 | 55 | /// Dispose resource. 56 | public func dispose() { 57 | disposable.dispose() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Sources/Mini/Dispatcher.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import NIOConcurrencyHelpers 19 | import RxSwift 20 | 21 | public typealias SubscriptionMap = SharedDictionary?> 22 | 23 | public final class Dispatcher { 24 | public struct DispatchMode { 25 | // swiftlint:disable:next type_name nesting 26 | public enum UI { 27 | case sync, async 28 | } 29 | } 30 | 31 | public var subscriptionCount: Int { 32 | return subscriptionMap.innerDictionary.mapValues { set -> Int in 33 | guard let setValue = set else { return 0 } 34 | return setValue.count 35 | } 36 | .reduce(0) { $0 + $1.value } 37 | } 38 | 39 | public static let defaultPriority = 100 40 | 41 | private let internalQueue = DispatchQueue(label: "MiniSwift", qos: .userInitiated) 42 | private var subscriptionMap = SubscriptionMap() 43 | private var middleware = [Middleware]() 44 | private var service = [Service]() 45 | private let root: RootChain 46 | private var chain: Chain 47 | private var dispatching: Bool = false 48 | private var subscriptionCounter: Atomic = Atomic(value: 0) 49 | 50 | public init() { 51 | root = RootChain(map: subscriptionMap) 52 | chain = root 53 | } 54 | 55 | private func build() -> Chain { 56 | return middleware.reduce(root) { (chain: Chain, middleware: Middleware) -> Chain in 57 | ForwardingChain { action in 58 | middleware.perform(action, chain) 59 | } 60 | } 61 | } 62 | 63 | public func add(middleware: Middleware) { 64 | internalQueue.sync { 65 | self.middleware.append(middleware) 66 | self.chain = build() 67 | } 68 | } 69 | 70 | public func remove(middleware: Middleware) { 71 | internalQueue.sync { 72 | if let index = self.middleware.firstIndex(where: { middleware.id == $0.id }) { 73 | self.middleware.remove(at: index) 74 | } 75 | chain = build() 76 | } 77 | } 78 | 79 | public func register(service: Service) { 80 | internalQueue.sync { 81 | self.service.append(service) 82 | } 83 | } 84 | 85 | public func unregister(service: Service) { 86 | internalQueue.sync { 87 | if let index = self.service.firstIndex(where: { service.id == $0.id }) { 88 | self.service.remove(at: index) 89 | } 90 | } 91 | } 92 | 93 | public func subscribe(priority: Int, tag: String, completion: @escaping (Action) -> Void) -> DispatcherSubscription { 94 | let subscription = DispatcherSubscription( 95 | dispatcher: self, 96 | id: getNewSubscriptionId(), 97 | priority: priority, 98 | tag: tag, 99 | completion: completion 100 | ) 101 | return registerInternal(subscription: subscription) 102 | } 103 | 104 | public func registerInternal(subscription: DispatcherSubscription) -> DispatcherSubscription { 105 | internalQueue.sync { 106 | if let map = subscriptionMap[subscription.tag, orPut: OrderedSet()] { 107 | map.insert(subscription) 108 | } 109 | } 110 | return subscription 111 | } 112 | 113 | public func unregisterInternal(subscription: DispatcherSubscription) { 114 | internalQueue.sync { 115 | var removed = false 116 | if let set = subscriptionMap[subscription.tag] as? OrderedSet { 117 | removed = set.remove(subscription) 118 | } else { 119 | removed = true 120 | } 121 | assert(removed, "Failed to remove DispatcherSubscription, multiple dispose calls?") 122 | } 123 | } 124 | 125 | public func subscribe(completion: @escaping (T) -> Void) -> DispatcherSubscription { 126 | return subscribe(tag: T.tag, completion: { (action: T) -> Void in 127 | completion(action) 128 | }) 129 | } 130 | 131 | public func subscribe(tag: String, completion: @escaping (T) -> Void) -> DispatcherSubscription { 132 | return subscribe(tag: tag, completion: { object in 133 | if let action = object as? T { 134 | completion(action) 135 | } else { 136 | fatalError("Casting to \(tag) failed") 137 | } 138 | }) 139 | } 140 | 141 | public func subscribe(tag: String, completion: @escaping (Action) -> Void) -> DispatcherSubscription { 142 | return subscribe(priority: Dispatcher.defaultPriority, tag: tag, completion: completion) 143 | } 144 | 145 | public func dispatch(_ action: Action, mode: Dispatcher.DispatchMode.UI) { 146 | switch mode { 147 | case .sync: 148 | if DispatchQueue.isMain { 149 | dispatch(action) 150 | } else { 151 | DispatchQueue.main.sync { 152 | self.dispatch(action) 153 | } 154 | } 155 | case .async: 156 | DispatchQueue.main.async { 157 | self.dispatch(action) 158 | } 159 | } 160 | } 161 | 162 | private func dispatch(_ action: Action) { 163 | assert(DispatchQueue.isMain) 164 | internalQueue.sync { 165 | defer { dispatching = false } 166 | if dispatching { 167 | preconditionFailure("Already dispatching") 168 | } 169 | dispatching = true 170 | _ = chain.proceed(action) 171 | internalQueue.async { [weak self] in 172 | guard let self = self else { return } 173 | self.service.forEach { 174 | $0.perform(action, self.chain) 175 | } 176 | } 177 | } 178 | } 179 | 180 | private func getNewSubscriptionId() -> Int { 181 | return subscriptionCounter.add(1) 182 | } 183 | } 184 | 185 | public final class DispatcherSubscription: Comparable, Disposable { 186 | private let dispatcher: Dispatcher 187 | public let id: Int 188 | private let priority: Int 189 | private let completion: (Action) -> Void 190 | 191 | public let tag: String 192 | 193 | public init(dispatcher: Dispatcher, 194 | id: Int, 195 | priority: Int, 196 | tag: String, 197 | completion: @escaping (Action) -> Void) { 198 | self.dispatcher = dispatcher 199 | self.id = id 200 | self.priority = priority 201 | self.tag = tag 202 | self.completion = completion 203 | } 204 | 205 | public func dispose() { 206 | dispatcher.unregisterInternal(subscription: self) 207 | } 208 | 209 | public func on(_ action: Action) { 210 | completion(action) 211 | } 212 | 213 | public static func == (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool { 214 | return lhs.id == rhs.id 215 | } 216 | 217 | public static func > (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool { 218 | return lhs.priority > rhs.priority 219 | } 220 | 221 | public static func < (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool { 222 | return lhs.priority < rhs.priority 223 | } 224 | 225 | public static func >= (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool { 226 | return lhs.priority >= rhs.priority 227 | } 228 | 229 | public static func <= (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool { 230 | return lhs.priority <= rhs.priority 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /Sources/Mini/Middleware.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public typealias MiddlewareChain = (Action, Chain) -> Action 20 | public typealias Next = (Action) -> Action 21 | 22 | public protocol Chain { 23 | var proceed: Next { get } 24 | } 25 | 26 | public protocol Middleware { 27 | var id: UUID { get } 28 | var perform: MiddlewareChain { get } 29 | } 30 | 31 | public final class ForwardingChain: Chain { 32 | private let next: Next 33 | 34 | public var proceed: Next { 35 | return { action in 36 | self.next(action) 37 | } 38 | } 39 | 40 | public init(next: @escaping Next) { 41 | self.next = next 42 | } 43 | } 44 | 45 | public final class RootChain: Chain { 46 | private let map: SubscriptionMap 47 | 48 | public var proceed: Next { 49 | return { action in 50 | if let set = self.map[action.innerTag] { 51 | set?.forEach { sub in 52 | sub.on(action) 53 | } 54 | } 55 | return action 56 | } 57 | } 58 | 59 | public init(map: SubscriptionMap) { 60 | self.map = map 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Sources/Mini/Mini.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /Sources/Mini/ReducerGroup.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | public protocol Group: Disposable { 21 | var disposeBag: CompositeDisposable { get } 22 | } 23 | 24 | public class ReducerGroup: Group { 25 | public let disposeBag = CompositeDisposable() 26 | 27 | public init(_ builder: Disposable...) { 28 | let disposable = builder 29 | disposable.forEach { _ = disposeBag.insert($0) } 30 | } 31 | 32 | public func dispose() { 33 | disposeBag.dispose() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Sources/Mini/Service.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public typealias ServiceChain = (Action, Chain) -> Void 20 | 21 | public protocol Service { 22 | var id: UUID { get } 23 | var perform: ServiceChain { get } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Mini/StateType.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public protocol StateType { 20 | func isEqual(to other: StateType) -> Bool 21 | } 22 | 23 | public extension StateType where Self: Equatable { 24 | func isEqual(to other: StateType) -> Bool { 25 | guard let other = other as? Self else { return false } 26 | return self == other 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/Mini/Store.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | public protocol StoreType { 21 | associatedtype State: StateType 22 | associatedtype StoreController: Disposable 23 | 24 | var state: State { get set } 25 | var dispatcher: Dispatcher { get } 26 | var reducerGroup: ReducerGroup { get } 27 | 28 | func replayOnce() 29 | } 30 | 31 | extension StoreType { 32 | /** 33 | Property responsible of reduce the `State` given a certain `Action` being triggered. 34 | ``` 35 | public var reducerGroup: ReducerGroup { 36 | ReducerGroup {[ 37 | Reducer(of: SomeAction.self, on: self.dispatcher) { (action: SomeAction) 38 | self.state = myCoolNewState 39 | }, 40 | Reducer(of: OtherAction.self, on: self.dispatcher) { (action: OtherAction) 41 | // Needed work 42 | self.state = myAnotherState 43 | } 44 | } 45 | ]} 46 | ``` 47 | - Note : The property has a default implementation which complies with the @_functionBuilder's current limitations, where no empty blocks can be produced in this iteration. 48 | */ 49 | public var reducerGroup: ReducerGroup { 50 | return ReducerGroup() 51 | } 52 | } 53 | 54 | public class Store: ObservableType, StoreType { 55 | public typealias Element = State 56 | 57 | public typealias State = State 58 | public typealias StoreController = StoreController 59 | 60 | public typealias ObjectWillChangePublisher = BehaviorSubject 61 | 62 | public var objectWillChange: ObjectWillChangePublisher 63 | 64 | private var _initialState: State 65 | public let dispatcher: Dispatcher 66 | public var storeController: StoreController 67 | 68 | private let queue = DispatchQueue(label: "atomic state") 69 | 70 | private var _state: State 71 | 72 | public var state: State { 73 | get { 74 | return _state 75 | } 76 | set { 77 | queue.sync { 78 | if !newValue.isEqual(to: _state) { 79 | _state = newValue 80 | objectWillChange.onNext(state) 81 | } 82 | } 83 | } 84 | } 85 | 86 | public var initialState: State { 87 | return _initialState 88 | } 89 | 90 | public init(_ state: State, 91 | dispatcher: Dispatcher, 92 | storeController: StoreController) { 93 | _initialState = state 94 | _state = state 95 | self.dispatcher = dispatcher 96 | objectWillChange = ObjectWillChangePublisher(value: state) 97 | self.storeController = storeController 98 | self.state = _initialState 99 | } 100 | 101 | public var reducerGroup: ReducerGroup { 102 | return ReducerGroup() 103 | } 104 | 105 | public func notify() { 106 | replayOnce() 107 | } 108 | 109 | public func replayOnce() { 110 | objectWillChange.onNext(state) 111 | } 112 | 113 | public func reset() { 114 | state = initialState 115 | } 116 | 117 | public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Store.Element { 118 | objectWillChange.subscribe(observer) 119 | } 120 | } 121 | 122 | public extension Store { 123 | func replaying() -> Observable { 124 | startWith(state) 125 | } 126 | } 127 | 128 | extension Store { 129 | public func dispatch(_ action: @autoclosure @escaping () -> A) -> Observable { 130 | let action = action() 131 | dispatcher.dispatch(action, mode: .sync) 132 | return objectWillChange.asObservable() 133 | } 134 | 135 | public func withStateChanges(in stateComponent: KeyPath) -> Observable { 136 | map(stateComponent) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Sources/Mini/Utils/Foundation/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Dictionary { 20 | /// Returns the value for the given key. If the key is not found in the map, calls the `defaultValue` function, 21 | /// puts its result into the map under the given key and returns it. 22 | public mutating func getOrPut(_ key: Key, defaultValue: @autoclosure () -> Value) -> Value { 23 | return self[key, orPut: defaultValue()] 24 | } 25 | 26 | public subscript(_ key: Key, orPut value: @autoclosure () -> Value) -> Value { 27 | mutating get { 28 | if let value = self[key] { 29 | return value 30 | } 31 | let value = value() 32 | self[key] = value 33 | return value 34 | } 35 | } 36 | 37 | public subscript(unwrapping key: Key) -> Value! { 38 | return self[key] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Mini/Utils/Foundation/DispatchQueue+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension DispatchQueue { 20 | private static var token: DispatchSpecificKey = { 21 | let key = DispatchSpecificKey() 22 | DispatchQueue.main.setSpecific(key: key, value: ()) 23 | return key 24 | }() 25 | 26 | static var isMain: Bool { 27 | return DispatchQueue.getSpecific(key: token) != nil 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Mini/Utils/Foundation/KeyPath.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | prefix operator ^ 20 | 21 | public prefix func ^ ( 22 | _ keypath: KeyPath 23 | ) -> (Root) -> Value { 24 | return { root in root[keyPath: keypath] } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Mini/Utils/Foundation/OptionalType.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | #if !canImport(RxOptional) 20 | 21 | public protocol OptionalType { 22 | associatedtype Wrapped 23 | var value: Wrapped? { get } 24 | } 25 | 26 | extension Optional: OptionalType { 27 | /// Cast `Optional` to `Wrapped?` 28 | public var value: Wrapped? { 29 | return self 30 | } 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Sources/Mini/Utils/OrderedSet.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /** 20 | An Ordered Set is a collection where all items in the set follow an ordering, 21 | usually ordered from 'least' to 'most'. The way you value and compare items 22 | can be user-defined. 23 | */ 24 | public class OrderedSet { 25 | private var internalSet = [T]() 26 | 27 | public init(initial: [T] = []) { 28 | insert(initial) 29 | } 30 | 31 | /// Returns the number of elements in the OrderedSet. 32 | public var count: Int { 33 | return internalSet.count 34 | } 35 | 36 | /// Inserts an item. Performance: O(n) 37 | @discardableResult 38 | public func insert(_ item: T) -> Bool { 39 | if exists(item) { 40 | return false // don't add an item if it already exists 41 | } 42 | 43 | // Insert new the item just before the one that is larger. 44 | for i in 0 ..< count where internalSet[i] > item { 45 | internalSet.insert(item, at: i) 46 | return true 47 | } 48 | 49 | // Append to the back if the new item is greater than any other in the set. 50 | internalSet.append(item) 51 | return true 52 | } 53 | 54 | /// Insert an array of items 55 | @discardableResult 56 | public func insert(_ items: [T]) -> Bool { 57 | var success = false 58 | items.forEach { 59 | success = insert($0) || success 60 | } 61 | return success 62 | } 63 | 64 | /// Removes an item if it exists. Performance: O(n) 65 | @discardableResult 66 | public func remove(_ item: T) -> Bool { 67 | if let index = indexOf(item) { 68 | internalSet.remove(at: index) 69 | return true 70 | } 71 | return false 72 | } 73 | 74 | /// Returns true if and only if the item exists somewhere in the set. 75 | public func exists(_ item: T) -> Bool { 76 | return indexOf(item) != nil 77 | } 78 | 79 | /// Returns the index of an item if it exists, or nil otherwise. 80 | public func indexOf(_ item: T) -> Int? { 81 | var leftBound = 0 82 | var rightBound = count - 1 83 | 84 | while leftBound <= rightBound { 85 | let mid = leftBound + ((rightBound - leftBound) / 2) 86 | 87 | if internalSet[mid] > item { 88 | rightBound = mid - 1 89 | continue 90 | } 91 | 92 | if internalSet[mid] < item { 93 | leftBound = mid + 1 94 | continue 95 | } 96 | 97 | if internalSet[mid] == item { 98 | return mid 99 | } 100 | 101 | // When we get here, we've landed on an item whose value is equal to the 102 | // value of the item we're looking for, but the items themselves are not 103 | // equal. We need to check the items with the same value to the right 104 | // and to the left in order to find an exact match. 105 | // Check to the right. 106 | for value in stride(from: mid, to: count - 1, by: 1) { 107 | if internalSet[value + 1] == item { 108 | return value + 1 109 | } 110 | if internalSet[value] < internalSet[value + 1] { 111 | break 112 | } 113 | } 114 | 115 | // Check to the left. 116 | for value in stride(from: mid, to: 0, by: -1) { 117 | if internalSet[value - 1] == item { 118 | return value - 1 119 | } 120 | if internalSet[value] > internalSet[value - 1] { 121 | break 122 | } 123 | } 124 | 125 | // value not found, the value are equal but the item not, break the loop: 126 | break 127 | } 128 | return nil 129 | } 130 | 131 | /// Returns the item at the given index. 132 | /// Assertion fails if the index is out of the range of [0, count). 133 | public subscript(index: Int) -> T { 134 | assert(index >= 0 && index < count) 135 | return internalSet[index] 136 | } 137 | 138 | /// Returns the 'maximum' or 'largest' value in the set. 139 | public var max: T? { 140 | return internalSet.isEmpty ? nil : internalSet[count - 1] 141 | } 142 | 143 | /// Returns the 'minimum' or 'smallest' value in the set. 144 | public var min: T? { 145 | return internalSet.isEmpty ? nil : internalSet[0] 146 | } 147 | 148 | /// Returns the k-th largest element in the set, if k is in the range 149 | /// [1, count]. Returns nil otherwise. 150 | public func kLargest(element: Int) -> T? { 151 | return element > count || element <= 0 ? nil : internalSet[count - element] 152 | } 153 | 154 | /// Returns the k-th smallest element in the set, if k is in the range 155 | /// [1, count]. Returns nil otherwise. 156 | public func kSmallest(element: Int) -> T? { 157 | return element > count || element <= 0 ? nil : internalSet[element - 1] 158 | } 159 | 160 | /// For each function 161 | public func forEach(_ body: (T) -> Swift.Void) { 162 | return (internalSet as NSArray) 163 | .enumerateObjects( 164 | options: .concurrent, 165 | using: { object, _, _ in 166 | guard let object = object as? T else { return } 167 | body(object) 168 | } 169 | ) 170 | } 171 | 172 | /// Enumerated function 173 | public func enumerated() -> EnumeratedSequence<[T]> { 174 | return internalSet.enumerated() 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Sources/Mini/Utils/RxSwift/ObservableType+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import RxSwift 19 | #if canImport(RxOptional) 20 | import RxOptional 21 | #endif 22 | 23 | extension ObservableType { 24 | /// Take the first element that matches the filter function. 25 | /// 26 | /// - Parameter fn: Filter closure. 27 | /// - Returns: The first element that matches the filter. 28 | public func filterOne(_ condition: @escaping (Element) -> Bool) -> Observable { 29 | filter { 30 | condition($0) 31 | }.take(1) 32 | } 33 | 34 | public func filter(_ keyPath: KeyPath) -> Observable { 35 | filter(^keyPath) 36 | } 37 | 38 | public func map(_ keyPath: KeyPath) -> Observable { 39 | map(^keyPath) 40 | } 41 | 42 | public func one() -> Observable { 43 | take(1) 44 | } 45 | 46 | public func skippingCurrent() -> Observable { 47 | skip(1) 48 | } 49 | 50 | /** 51 | Selects a property component from an `Element` filtering `nil` and emitting only distinct contiguous elements. 52 | */ 53 | public func select(_ keyPath: KeyPath) -> Observable where T.Wrapped: Equatable { 54 | map(keyPath) 55 | .filterNil() 56 | .distinctUntilChanged() 57 | } 58 | } 59 | 60 | #if !canImport(RxOptional) 61 | public extension ObservableType where Element: OptionalType { 62 | /** 63 | Unwraps and filters out `nil` elements. 64 | - returns: `Observable` of source `Observable`'s elements, with `nil` elements filtered out. 65 | */ 66 | func filterNil() -> Observable { 67 | return flatMap { element -> Observable in 68 | guard let value = element.value else { 69 | return Observable.empty() 70 | } 71 | return Observable.just(value) 72 | } 73 | } 74 | } 75 | #endif 76 | 77 | extension ObservableType where Element: StateType { 78 | /** 79 | Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes. 80 | */ 81 | public func withStateChanges(in stateComponent: KeyPath, that componentProperty: KeyPath) -> Observable { 82 | return map(stateComponent).filter(componentProperty) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Sources/Mini/Utils/SharedDictionary.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | /// Wrapper class to allow pass dictionaries with a memory reference 20 | public class SharedDictionary { 21 | public var innerDictionary: [Key: Value] 22 | 23 | public init() { 24 | innerDictionary = [:] 25 | } 26 | 27 | public func getOrPut(_ key: Key, defaultValue: @autoclosure () -> Value) -> Value { 28 | return self[key, orPut: defaultValue()] 29 | } 30 | 31 | public func get(withKey key: Key) -> Value? { 32 | return self[key] 33 | } 34 | 35 | public subscript(_ key: Key, orPut defaultValue: @autoclosure () -> Value) -> Value { 36 | return innerDictionary[key, orPut: defaultValue()] 37 | } 38 | 39 | public subscript(_ key: Key) -> Value? { 40 | return innerDictionary[key] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/MiniPromises/Box.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Dispatch 18 | 19 | enum Sealant { 20 | case idle 21 | case pending 22 | case resolved(R) 23 | case completed 24 | } 25 | 26 | /// - Remark: not protocol ∵ http://www.russbishop.net/swift-associated-types-cont 27 | class Box { 28 | func inspect() -> Sealant { fatalError() } 29 | func seal(_: T) {} 30 | func fill(_: Sealant) { fatalError() } 31 | } 32 | 33 | final class SealedBox: Box { 34 | let value: T 35 | 36 | init(value: T) { 37 | self.value = value 38 | super.init() 39 | } 40 | 41 | override func inspect() -> Sealant { 42 | return .resolved(value) 43 | } 44 | } 45 | 46 | final class PreSealedBox: Box { 47 | private var sealant: Sealant = .completed 48 | 49 | override init() { 50 | super.init() 51 | } 52 | 53 | override func inspect() -> Sealant { 54 | return sealant 55 | } 56 | 57 | @available(iOS, unavailable) 58 | @available(OSX, unavailable) 59 | @available(watchOS, unavailable) 60 | override func seal(_: T) { 61 | // NO-OP 62 | } 63 | 64 | @available(iOS, unavailable) 65 | @available(OSX, unavailable) 66 | @available(watchOS, unavailable) 67 | override func fill(_: Sealant) { 68 | // NO-OP 69 | } 70 | } 71 | 72 | class EmptyBox: Box { 73 | private var sealant: Sealant = .pending 74 | 75 | override func fill(_ sealant: Sealant) { 76 | switch sealant { 77 | case .idle, .pending: 78 | self.sealant = sealant 79 | default: 80 | return // don't seal the promise 81 | } 82 | } 83 | 84 | override func seal(_ value: T) { 85 | switch self.sealant { 86 | case .pending: 87 | self.sealant = .resolved(value) 88 | case .idle, .resolved, .completed: 89 | return // cannot be mutated! 90 | } 91 | } 92 | 93 | override func inspect() -> Sealant { 94 | return self.sealant 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/MiniPromises/MiniPromises.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #if canImport(Mini) 18 | @_exported import Mini 19 | #endif 20 | -------------------------------------------------------------------------------- /Sources/MiniPromises/Promise.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public protocol PromiseType { 20 | associatedtype Element 21 | 22 | var result: Result? { get } 23 | 24 | var isIdle: Bool { get } 25 | var isPending: Bool { get } 26 | var isResolved: Bool { get } 27 | var isFulfilled: Bool { get } 28 | var isRejected: Bool { get } 29 | var value: Element? { get } 30 | var error: Swift.Error? { get } 31 | 32 | func resolve(_ result: Result?) -> Self? 33 | func fulfill(_ value: Element) -> Self 34 | func reject(_ error: Swift.Error) -> Self 35 | } 36 | 37 | @dynamicCallable 38 | @dynamicMemberLookup 39 | public final class Promise: PromiseType { 40 | public typealias Element = T 41 | 42 | private typealias PromiseBox = Box> 43 | 44 | private var properties: [String: Any] = [:] 45 | 46 | private let box: PromiseBox 47 | 48 | fileprivate init(box: SealedBox>) { 49 | self.box = box 50 | } 51 | 52 | public class func value(_ value: T) -> Promise { 53 | return Promise(box: SealedBox(value: Result.success(value))) 54 | } 55 | 56 | public class func error(_ error: Swift.Error) -> Promise { 57 | return Promise(box: SealedBox(value: Result.failure(error))) 58 | } 59 | 60 | public init(error: Swift.Error) { 61 | box = SealedBox(value: Result.failure(error)) 62 | } 63 | 64 | private init(_ sealant: Sealant>, options: [String: Any] = [:]) { 65 | box = EmptyBox() 66 | box.fill(sealant) 67 | properties = options 68 | } 69 | 70 | private init(with box: @autoclosure @escaping () -> Box>) { 71 | self.box = box() 72 | } 73 | 74 | public init() { 75 | box = EmptyBox() 76 | } 77 | 78 | public class func idle(with options: [String: Any] = [:]) -> Promise { 79 | return Promise(.idle, options: options) 80 | } 81 | 82 | public class func pending(options: [String: Any] = [:]) -> Promise { 83 | return Promise(.pending, options: options) 84 | } 85 | 86 | public var result: Result? { 87 | switch box.inspect() { 88 | case .idle, .pending, .completed: 89 | return nil 90 | case let .resolved(result): 91 | return result 92 | } 93 | } 94 | 95 | /// - Note: `fulfill` do not trigger an object reassignment, 96 | /// so no notifications about it can be triggered. It is recommended 97 | /// to call the method `notify` afterwards. 98 | @discardableResult 99 | public func fulfill(_ value: T) -> Self { 100 | box.seal(.success(value)) 101 | return self 102 | } 103 | 104 | /// - Note: `reject` do not trigger an object reassignment, 105 | /// so no notifications about it can be triggered. It is recommended 106 | /// to call the method `notify` afterwards. 107 | @discardableResult 108 | public func reject(_ error: Swift.Error) -> Self { 109 | box.seal(.failure(error)) 110 | return self 111 | } 112 | 113 | /// Resolves the current `Promise` with the optional `Result` parameter. 114 | /// - Returns: `self` or `nil` if no `result` was not provided. 115 | /// - Note: The optional parameter and restun value are helpers in order to 116 | /// make optional chaining in the `Reducer` context. 117 | @discardableResult 118 | public func resolve(_ result: Result?) -> Self? { 119 | if let result = result { 120 | box.seal(result) 121 | return self 122 | } 123 | return nil 124 | } 125 | 126 | public subscript(dynamicMember member: String) -> Value? { 127 | return properties[member] as? Value 128 | } 129 | 130 | public func dynamicallyCall(withKeywordArguments args: KeyValuePairs) { 131 | guard let key = args.first?.key, let value = args.first?.value else { return } 132 | properties[key] = value 133 | } 134 | } 135 | 136 | extension Dictionary where Key: StringProtocol {} 137 | 138 | public extension Promise { 139 | /** 140 | - Returns: `true` if the promise has not yet resolved nor pending. 141 | */ 142 | var isIdle: Bool { 143 | if case .idle = box.inspect() { 144 | return true 145 | } 146 | return false 147 | } 148 | 149 | /** 150 | - Returns: `true` if the promise has not yet resolved. 151 | */ 152 | var isPending: Bool { 153 | return !isIdle && (result == nil && !isCompleted) 154 | } 155 | 156 | /** 157 | - Returns: `true` if the promise has completed. 158 | */ 159 | var isCompleted: Bool { 160 | switch box.inspect() { 161 | case .completed, .resolved: 162 | return true 163 | default: 164 | return false 165 | } 166 | } 167 | 168 | /** 169 | - Returns: `true` if the promise has resolved. 170 | */ 171 | var isResolved: Bool { 172 | return !isIdle && !isPending 173 | } 174 | 175 | /** 176 | - Returns: `true` if the promise was fulfilled. 177 | */ 178 | var isFulfilled: Bool { 179 | return value != nil 180 | } 181 | 182 | /** 183 | - Returns: `true` if the promise was rejected. 184 | */ 185 | var isRejected: Bool { 186 | return error != nil 187 | } 188 | 189 | /** 190 | - Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected. 191 | */ 192 | var value: T? { 193 | switch result { 194 | case .none: 195 | return nil 196 | case let .some(.success(value)): 197 | return value 198 | case .some(.failure): 199 | return nil 200 | } 201 | } 202 | 203 | /** 204 | - Returns: The error with which this promise was rejected or `nil` if this promise is pending or fulfilled. 205 | */ 206 | var error: Swift.Error? { 207 | switch result { 208 | case .none: 209 | return nil 210 | case .some(.success): 211 | return nil 212 | case let .some(.failure(error)): 213 | return error 214 | } 215 | } 216 | } 217 | 218 | extension Promise where T == () { 219 | public convenience init() { 220 | self.init(box: SealedBox>(value: .success(()))) 221 | } 222 | 223 | public static func empty() -> Promise { 224 | self.init() 225 | } 226 | } 227 | 228 | extension Promise: Equatable where T == () { 229 | public static func == (lhs: Promise, rhs: Promise) -> Bool { 230 | return ObjectIdentifier(lhs) == ObjectIdentifier(rhs) 231 | } 232 | } 233 | 234 | extension Promise where T: Equatable { 235 | public static func == (lhs: Promise, rhs: Promise) -> Bool { 236 | guard lhs.value == rhs.value else { return false } 237 | guard lhs.isIdle == rhs.isIdle else { return false } 238 | guard lhs.isResolved == rhs.isResolved else { return false } 239 | guard lhs.isRejected == rhs.isRejected else { return false } 240 | guard lhs.isPending == rhs.isPending else { return false } 241 | guard lhs.isCompleted == rhs.isCompleted else { return false } 242 | if 243 | let result1 = lhs.result, 244 | let result2 = rhs.result { 245 | if 246 | case .failure = result1, 247 | case .failure = result2 { 248 | return true 249 | } 250 | guard 251 | case let .success(value1) = result1, 252 | case let .success(value2) = result2 else { return false } 253 | guard value1 == value2 else { return false } 254 | } 255 | return true 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /Sources/MiniPromises/Utils/Extensions/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | extension Dictionary where Value: PromiseType, Key: Hashable { 20 | public subscript(promise key: Key) -> Value { 21 | return self[unwrapping: key] 22 | } 23 | 24 | public func hasValue(for key: Dictionary.Key) -> Bool { 25 | return keys.contains(key) 26 | } 27 | 28 | func notify(to store: T) -> Self { 29 | store.replayOnce() 30 | return self 31 | } 32 | 33 | @discardableResult 34 | public func resolve(with other: [Key: Value]) -> Self { 35 | return merging(other, uniquingKeysWith: { _, new in new }) 36 | } 37 | 38 | public func mergingNew(with other: [Key: Value]) -> Self { 39 | return merging(other, uniquingKeysWith: { _, new in new }) 40 | } 41 | } 42 | 43 | public extension Dictionary where Value: PromiseType, Key: Hashable, Value.Element: Equatable { 44 | static func == (lhs: [Key: Value], rhs: [Key: Value]) -> Bool { 45 | guard lhs.keys == rhs.keys else { return false } 46 | for (key1, key2) in zip( 47 | lhs.keys.sorted(by: { $0.hashValue < $1.hashValue }), 48 | rhs.keys.sorted(by: { $0.hashValue < $1.hashValue }) 49 | ) { 50 | guard 51 | let left: Promise = lhs[key1] as? Promise, 52 | let right: Promise = rhs[key2] as? Promise else { return false } 53 | guard left == right else { return false } 54 | } 55 | return true 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/MiniPromises/Utils/Extensions/Store+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public extension Promise { 20 | func notify(to store: T) { 21 | store.replayOnce() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/MiniPromises/Utils/PayloadAction.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import Mini 19 | 20 | public protocol PayloadAction { 21 | associatedtype Payload 22 | 23 | init(promise: Promise) 24 | } 25 | 26 | public protocol CompletableAction: Action & PayloadAction {} 27 | 28 | public protocol EmptyAction: Action & PayloadAction where Payload == Swift.Void { 29 | init(promise: Promise) 30 | } 31 | 32 | public extension EmptyAction { 33 | init(promise _: Promise) { 34 | fatalError("Never call this method from a EmptyAction") 35 | } 36 | } 37 | 38 | public protocol KeyedPayloadAction { 39 | associatedtype Payload 40 | associatedtype Key: Hashable 41 | 42 | init(promise: [Key: Promise]) 43 | } 44 | 45 | public protocol KeyedCompletableAction: Action & KeyedPayloadAction {} 46 | -------------------------------------------------------------------------------- /Sources/MiniPromises/Utils/RxSwift/ObservableType+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | public enum Promises {} 21 | 22 | public extension Promises { 23 | enum Lifetime { 24 | case once 25 | case forever(ignoringOld: Bool = false) 26 | } 27 | } 28 | 29 | extension ObservableType where Self.Element: StateType { 30 | private func filterForLifetime( 31 | taskMap: @escaping ((Self.Element) -> Promise?), 32 | lifetime: Promises.Lifetime 33 | ) -> Observable { 34 | switch lifetime { 35 | case .once: 36 | return filterOne { taskMap($0)?.isResolved ?? true } 37 | case let .forever(ignoreOld): 38 | let date = Date() 39 | return skipWhile { 40 | if ignoreOld { 41 | if let promise = taskMap($0), let promiseDate: Date = promise.date { 42 | return promiseDate < date 43 | } 44 | return false 45 | } else { 46 | return false 47 | } 48 | } 49 | .filter { taskMap($0)?.isResolved ?? true } 50 | } 51 | } 52 | 53 | private func filterForKeyedLifetime( 54 | key: K, 55 | taskMap: @escaping ((Self.Element) -> [K: Promise]), 56 | lifetime: Promises.Lifetime 57 | ) -> Observable { 58 | switch lifetime { 59 | case .once: 60 | return filter { taskMap($0).hasValue(for: key) } 61 | .filter { taskMap($0)[promise: key].isResolved } 62 | case .forever: 63 | return skipWhile { taskMap($0)[promise: key].isPending || taskMap($0)[promise: key].isResolved } 64 | .filter { taskMap($0).hasValue(for: key) } 65 | .filter { taskMap($0)[promise: key].isResolved } 66 | .take(1) 67 | } 68 | } 69 | 70 | private func subscribe( 71 | taskMap: @escaping ((Self.Element) -> Promise?), 72 | lifetime: Promises.Lifetime = .once, 73 | success: @escaping (Self.Element) -> Void = { _ in }, 74 | error: @escaping (Self.Element) -> Void = { _ in } 75 | ) 76 | -> Disposable { 77 | return filterForLifetime(taskMap: taskMap, lifetime: lifetime) 78 | .subscribe(onNext: { state in 79 | if let promise = taskMap(state) { 80 | if case .success? = promise.result { 81 | success(state) 82 | } 83 | if case .failure? = promise.result { 84 | error(state) 85 | } 86 | } 87 | }) 88 | } 89 | 90 | private func subscribe( 91 | key: K, 92 | taskMap: @escaping ((Self.Element) -> [K: Promise]), 93 | lifetime: Promises.Lifetime = .once, 94 | success: @escaping (Self.Element) -> Void = { _ in }, 95 | error: @escaping (Self.Element) -> Void = { _ in } 96 | ) 97 | -> Disposable { 98 | return filterForKeyedLifetime(key: key, taskMap: taskMap, lifetime: lifetime) 99 | .subscribe(onNext: { state in 100 | let promise = taskMap(state)[promise: key] 101 | if case .success? = promise.result { 102 | success(state) 103 | } 104 | if case .failure? = promise.result { 105 | error(state) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | extension ObservableType where Element: StoreType & ObservableType, Self.Element.State == Self.Element.Element { 112 | @available(*, deprecated, message: "Use store.dispatch() or dispatcher.dispatch in conjunction with an Observable over the Store: withStateChanges, select") 113 | public static func dispatch>( 114 | using dispatcher: Dispatcher? = nil, 115 | factory action: @autoclosure @escaping () -> A, 116 | taskMap: @escaping (Self.Element.State) -> T?, 117 | on store: Self.Element, 118 | lifetime: Promises.Lifetime = .once 119 | ) 120 | -> Observable { 121 | let observable: Observable = Observable.create { observer in 122 | let action = action() 123 | let dispatcher = dispatcher ?? store.dispatcher 124 | dispatcher.dispatch(action, mode: .sync) 125 | let subscription = store.subscribe( 126 | taskMap: taskMap, 127 | lifetime: lifetime, 128 | success: { state in 129 | observer.on(.next(state)) 130 | }, 131 | error: { state in 132 | if let task = taskMap(state), let error = task.error { 133 | observer.on(.error(error)) 134 | } 135 | } 136 | ) 137 | return Disposables.create([subscription]) 138 | } 139 | return observable 140 | } 141 | 142 | @available(*, deprecated, message: "Use store.dispatch() or dispatcher.dispatch in conjunction with an Observable over the Store: withStateChanges, select") 143 | public static func dispatch>( 144 | using dispatcher: Dispatcher? = nil, 145 | factory action: @autoclosure @escaping () -> A, 146 | key: K, 147 | taskMap: @escaping (Self.Element.State) -> [K: T], 148 | on store: Self.Element, 149 | lifetime: Promises.Lifetime = .once 150 | ) 151 | -> Observable { 152 | let observable: Observable = Observable.create { observer in 153 | let dispatcher = dispatcher ?? store.dispatcher 154 | let action = action() 155 | dispatcher.dispatch(action, mode: .sync) 156 | let subscription = store.subscribe( 157 | key: key, 158 | taskMap: taskMap, 159 | lifetime: lifetime, 160 | success: { state in 161 | observer.on(.next(state)) 162 | }, 163 | error: { state in 164 | if let task = taskMap(state)[key], let error = task.error { 165 | observer.on(.error(error)) 166 | } 167 | } 168 | ) 169 | return Disposables.create([subscription]) 170 | } 171 | return observable 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /Sources/MiniPromises/Utils/RxSwift/PrimitiveSequenceType+Extensions.swift: -------------------------------------------------------------------------------- 1 | /// * 2 | // Copyright [2019] [BQ] 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // */ 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | public extension PrimitiveSequenceType where Self: ObservableConvertibleType, Self.Trait == SingleTrait { 21 | func dispatch(action: A.Type, 22 | on dispatcher: Dispatcher, 23 | mode: Dispatcher.DispatchMode.UI = .async, 24 | fillOnError errorPayload: A.Payload? = nil) 25 | -> Disposable where A.Payload == Self.Element { 26 | let subscription = subscribe( 27 | onSuccess: { payload in 28 | let action = A(promise: .value(payload)) 29 | dispatcher.dispatch(action, mode: mode) 30 | }, 31 | onError: { error in 32 | var action: A 33 | if let errorPayload = errorPayload { 34 | action = A(promise: .value(errorPayload)) 35 | } else { 36 | action = A(promise: .error(error)) 37 | } 38 | dispatcher.dispatch(action, mode: mode) 39 | } 40 | ) 41 | return subscription 42 | } 43 | 44 | func dispatch(action: A.Type, 45 | key: A.Key, 46 | on dispatcher: Dispatcher, 47 | mode: Dispatcher.DispatchMode.UI = .async, 48 | fillOnError errorPayload: A.Payload? = nil) 49 | -> Disposable where A.Payload == Self.Element { 50 | let subscription = subscribe( 51 | onSuccess: { payload in 52 | let action = A(promise: [key: .value(payload)]) 53 | dispatcher.dispatch(action, mode: mode) 54 | }, 55 | onError: { error in 56 | var action: A 57 | if let errorPayload = errorPayload { 58 | action = A(promise: [key: .value(errorPayload)]) 59 | } else { 60 | action = A(promise: [key: .error(error)]) 61 | } 62 | dispatcher.dispatch(action, mode: mode) 63 | } 64 | ) 65 | return subscription 66 | } 67 | 68 | func action(_ action: A.Type, 69 | fillOnError errorPayload: A.Payload? = nil) 70 | -> Single where A.Payload == Self.Element { 71 | return Single.create { single in 72 | let subscription = self.subscribe( 73 | onSuccess: { payload in 74 | let action = A(promise: .value(payload)) 75 | single(.success(action)) 76 | }, 77 | onError: { error in 78 | var action: A 79 | if let errorPayload = errorPayload { 80 | action = A(promise: .value(errorPayload)) 81 | } else { 82 | action = A(promise: .error(error)) 83 | } 84 | single(.success(action)) 85 | } 86 | ) 87 | return Disposables.create([subscription]) 88 | } 89 | } 90 | } 91 | 92 | public extension PrimitiveSequenceType where Trait == CompletableTrait, Element == Swift.Never { 93 | func dispatch(action: A.Type, 94 | on dispatcher: Dispatcher, 95 | mode: Dispatcher.DispatchMode.UI = .async) 96 | -> Disposable { 97 | let subscription = subscribe { completable in 98 | switch completable { 99 | case .completed: 100 | let action = A(promise: .empty()) 101 | dispatcher.dispatch(action, mode: mode) 102 | case let .error(error): 103 | let action = A(promise: .error(error)) 104 | dispatcher.dispatch(action, mode: mode) 105 | } 106 | } 107 | return subscription 108 | } 109 | 110 | func action(_ action: A.Type) 111 | -> Single { 112 | return Single.create { single in 113 | let subscription = self.subscribe { event in 114 | switch event { 115 | case .completed: 116 | let action = A(promise: .empty()) 117 | single(.success(action)) 118 | case let .error(error): 119 | let action = A(promise: .error(error)) 120 | single(.success(action)) 121 | } 122 | } 123 | return Disposables.create([subscription]) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Sources/MiniTasks/MiniTasks.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | #if canImport(Mini) 18 | @_exported import Mini 19 | #endif 20 | -------------------------------------------------------------------------------- /Sources/MiniTasks/Task.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public typealias AnyTask = TypedTask 20 | public typealias KeyedTask = [K: AnyTask] 21 | 22 | @dynamicMemberLookup 23 | public class TypedTask: Equatable { 24 | public enum Status { 25 | case idle 26 | case running 27 | case success 28 | case error 29 | } 30 | 31 | public let status: Status 32 | public let data: T? 33 | public let error: Error? 34 | public let initDate = Date() 35 | 36 | public init(status: Status = .idle, 37 | data: T? = nil, 38 | error: Error? = nil) { 39 | self.status = status 40 | self.data = data 41 | self.error = error 42 | } 43 | 44 | public static func idle() -> AnyTask { 45 | AnyTask(status: .idle) 46 | } 47 | 48 | public static func running() -> AnyTask { 49 | AnyTask(status: .running) 50 | } 51 | 52 | public static func success(_ data: T? = nil) -> TypedTask { 53 | TypedTask(status: .success, data: data) 54 | } 55 | 56 | public static func success() -> AnyTask { 57 | AnyTask(status: .success) 58 | } 59 | 60 | public static func failure(_ error: Error) -> AnyTask { 61 | AnyTask(status: .error, error: error) 62 | } 63 | 64 | public var isRunning: Bool { 65 | status == .running 66 | } 67 | 68 | public var isCompleted: Bool { 69 | status == .success || status == .error 70 | } 71 | 72 | public var isSuccessful: Bool { 73 | status == .success 74 | } 75 | 76 | public var isFailure: Bool { 77 | status == .error 78 | } 79 | 80 | private var properties: [String: Any] = [:] 81 | 82 | public subscript(dynamicMember member: String) -> Value? { 83 | get { 84 | properties[member] as? Value 85 | } 86 | set { 87 | properties[member] = newValue 88 | } 89 | } 90 | 91 | public static func == (lhs: TypedTask, rhs: TypedTask) -> Bool { 92 | lhs.status == rhs.status && lhs.initDate == rhs.initDate 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/MiniTasks/Utils/PayloadAction.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | 19 | public protocol PayloadAction { 20 | associatedtype Payload 21 | 22 | init(task: AnyTask, payload: Payload?) 23 | } 24 | 25 | public protocol CompletableAction: Action & PayloadAction {} 26 | 27 | public protocol EmptyAction: Action & PayloadAction where Payload == Swift.Never { 28 | init(task: AnyTask) 29 | } 30 | 31 | public extension EmptyAction { 32 | init(task _: AnyTask, payload _: Payload?) { 33 | fatalError("Never call this method from a EmptyAction") 34 | } 35 | } 36 | 37 | public protocol KeyedPayloadAction { 38 | associatedtype Payload 39 | associatedtype Key: Hashable 40 | 41 | init(task: AnyTask, payload: Payload?, key: Key) 42 | } 43 | 44 | public protocol KeyedCompletableAction: Action & KeyedPayloadAction {} 45 | -------------------------------------------------------------------------------- /Sources/MiniTasks/Utils/RxSwift/ObservableType+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | import RxSwift 19 | 20 | public extension ObservableType where Element: StateType { 21 | /** 22 | Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes. 23 | */ 24 | func withStateChanges( 25 | in stateComponent: KeyPath 26 | ) -> Observable { 27 | return map(stateComponent) 28 | } 29 | 30 | /** 31 | Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes using a `taskComponent` (i.e. a Task component in the State) to be completed (either successfully or failed). 32 | */ 33 | func withStateChanges>( 34 | in stateComponent: KeyPath, 35 | that taskComponent: KeyPath 36 | ) 37 | -> Observable { 38 | filter(taskComponent.appending(path: \.isCompleted)) 39 | .map(stateComponent) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Sources/MiniTasks/Utils/RxSwift/PrimitiveSequenceType+Extensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import RxSwift 18 | 19 | public extension PrimitiveSequenceType where Self: ObservableConvertibleType, Self.Trait == SingleTrait { 20 | /** 21 | Dispatches an given action from the result of the `Single` trait. This is only usable when the `Action` is a `CompletableAction`. 22 | - Parameter action: The `CompletableAction` type to be dispatched. 23 | - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 24 | - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 25 | - Parameter fillOnError: The payload that will replace the action's payload in case of failure. 26 | */ 27 | func dispatch(action: A.Type, 28 | on dispatcher: Dispatcher, 29 | mode: Dispatcher.DispatchMode.UI = .async, 30 | fillOnError errorPayload: A.Payload? = nil) 31 | -> Disposable where A.Payload == Self.Element { 32 | let subscription = subscribe( 33 | onSuccess: { payload in 34 | let action = A(task: .success(), payload: payload) 35 | dispatcher.dispatch(action, mode: mode) 36 | }, 37 | onError: { error in 38 | var action: A 39 | if let errorPayload = errorPayload { 40 | action = A(task: .success(), payload: errorPayload) 41 | } else { 42 | action = A(task: .failure(error), payload: errorPayload) 43 | } 44 | dispatcher.dispatch(action, mode: mode) 45 | } 46 | ) 47 | return subscription 48 | } 49 | 50 | /** 51 | Dispatches an given action from the result of the `Single` trait. This is only usable when the `Action` is a `CompletableAction`. 52 | - Parameter action: The `CompletableAction` type to be dispatched. 53 | - Parameter key: The key associated with the `Task` result. 54 | - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 55 | - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 56 | - Parameter fillOnError: The payload that will replace the action's payload in case of failure or `nil`. 57 | */ 58 | func dispatch(action: A.Type, 59 | key: A.Key, 60 | on dispatcher: Dispatcher, 61 | mode: Dispatcher.DispatchMode.UI = .async, 62 | fillOnError errorPayload: A.Payload? = nil) 63 | -> Disposable where A.Payload == Self.Element { 64 | let subscription = subscribe( 65 | onSuccess: { payload in 66 | let action = A(task: .success(), payload: payload, key: key) 67 | dispatcher.dispatch(action, mode: mode) 68 | }, 69 | onError: { error in 70 | var action: A 71 | if let errorPayload = errorPayload { 72 | action = A(task: .success(), payload: errorPayload, key: key) 73 | } else { 74 | action = A(task: .failure(error), payload: errorPayload, key: key) 75 | } 76 | dispatcher.dispatch(action, mode: mode) 77 | } 78 | ) 79 | return subscription 80 | } 81 | 82 | /** 83 | Builds a `CompletableAction` from a `Single` 84 | - Parameter action: The `CompletableAction` type to be built. 85 | - Parameter fillOnError: The payload that will replace the action's payload in case of failure or `nil`. 86 | - Returns: A `Single` of the `CompletableAction` type declared by the action parameter. 87 | */ 88 | func action(_ action: A.Type, 89 | fillOnError errorPayload: A.Payload? = nil) 90 | -> Single where A.Payload == Self.Element { 91 | return Single.create { single in 92 | let subscription = self.subscribe( 93 | onSuccess: { payload in 94 | let action = A(task: .success(), payload: payload) 95 | single(.success(action)) 96 | }, 97 | onError: { error in 98 | var action: A 99 | if let errorPayload = errorPayload { 100 | action = A(task: .success(), payload: errorPayload) 101 | } else { 102 | action = A(task: .failure(error), payload: errorPayload) 103 | } 104 | single(.success(action)) 105 | } 106 | ) 107 | return Disposables.create([subscription]) 108 | } 109 | } 110 | } 111 | 112 | public extension PrimitiveSequenceType where Trait == CompletableTrait, Element == Swift.Never { 113 | /** 114 | Dispatches an given action from the result of the `Completable` trait. This is only usable when the `Action` is an `EmptyAction`. 115 | - Parameter action: The `CompletableAction` type to be dispatched. 116 | - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 117 | - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 118 | */ 119 | func dispatch(action: A.Type, 120 | on dispatcher: Dispatcher, 121 | mode: Dispatcher.DispatchMode.UI = .async) 122 | -> Disposable { 123 | let subscription = subscribe { completable in 124 | switch completable { 125 | case .completed: 126 | let action = A(task: .success()) 127 | dispatcher.dispatch(action, mode: mode) 128 | case let .error(error): 129 | let action = A(task: .failure(error)) 130 | dispatcher.dispatch(action, mode: mode) 131 | } 132 | } 133 | return subscription 134 | } 135 | 136 | /** 137 | Builds an `EmptyAction` from a `Completable` 138 | - Parameter action: The `EmptyAction` type to be built. 139 | - Returns: A `Single` of the `EmptyAction` type declared by the action parameter. 140 | */ 141 | func action(_ action: A.Type) 142 | -> Single { 143 | return Single.create { single in 144 | let subscription = self.subscribe { event in 145 | switch event { 146 | case .completed: 147 | let action = A(task: .success()) 148 | single(.success(action)) 149 | case let .error(error): 150 | let action = A(task: .failure(error)) 151 | single(.success(action)) 152 | } 153 | } 154 | return Disposables.create([subscription]) 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /Sources/TestMiddleware/TestMiddleware.swift: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2019] [BQ] 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | import Foundation 18 | #if canImport(Mini) 19 | import Mini 20 | 21 | /// Action for testing purposes. 22 | public class TestOnlyAction: Action {} 23 | 24 | /// Interceptor class for testing purposes which mute all the received actions. 25 | public class TestMiddleware: Middleware { 26 | public var id: UUID = UUID() 27 | 28 | private var interceptedActions: [Action] = [] 29 | 30 | public var perform: MiddlewareChain { 31 | return { action, _ -> Action in 32 | self.interceptedActions.append(action) 33 | return TestOnlyAction() 34 | } 35 | } 36 | 37 | public init() {} 38 | 39 | /// Check for actions of certain type being intercepted. 40 | /// 41 | /// - Parameter kind: Action type to be checked against the intercepted actions. 42 | /// - Returns: Array of actions of `kind` being intercepted. 43 | public func actions(of _: T.Type) -> [T] { 44 | return interceptedActions.compactMap { $0 as? T } 45 | } 46 | 47 | public func action(of _: T.Type, where params: (T) -> Bool) -> Bool { 48 | interceptedActions.compactMap { $0 as? T }.compactMap(params).first ?? false 49 | } 50 | 51 | /// Clear all the intercepted actions 52 | public func clear() { 53 | interceptedActions.removeAll() 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /Templates/Autocopy.stencil: -------------------------------------------------------------------------------- 1 | {# Thanks to: https://arasthel.com/data-classes-on-swift/#selectwhichvalueswillbechangedandwhichwillbecopied #} 2 | // swiftlint:disable all 3 | public protocol AutoCopy { } 4 | 5 | public extension AutoCopy { 6 | 7 | func setValueOptional(_ value: OptionalCopyValue, _ defaultValue: T?) -> T? { 8 | switch(value) { 9 | case let .new(content): 10 | return content 11 | case .same: 12 | return defaultValue 13 | default: 14 | return nil 15 | } 16 | } 17 | } 18 | 19 | public enum OptionalCopyValue: ExpressibleByNilLiteral { 20 | 21 | case new(T) 22 | case same 23 | case `nil` 24 | 25 | public init(nilLiteral: ()) { 26 | self = .nil 27 | } 28 | } 29 | 30 | prefix operator * 31 | 32 | prefix func * (lhs: T) -> OptionalCopyValue { 33 | return .new(lhs) 34 | } 35 | 36 | // swiftlint:enable all 37 | 38 | {# Thanks to: https://arasthel.com/data-classes-on-swift/#selectwhichvalueswillbechangedandwhichwillbecopied #} 39 | 40 | {% for type in types.all where type.implements.AutoCopy %} 41 | {% if type|annotated:"import" %} 42 | {% for import in type.annotations.import|toArray %} 43 | import {{ import }} 44 | {% endfor %} 45 | {% endif %} 46 | // swiftlint:disable all 47 | extension {{ type.name }} { 48 | 49 | func copy( 50 | {% for variable in type.storedVariables %} 51 | {{ variable.name }} copied_{{ variable.name }}: OptionalCopyValue<{{ variable.unwrappedTypeName }}> = .same{% if not forloop.last %}, {% endif %} 52 | {% endfor %} 53 | ) -> {{ type.name }} { 54 | return {{ type.name }}( 55 | {% for variable in type.storedVariables %} 56 | {% if variable.isOptional %} 57 | {{ variable.name }}: setValueOptional(copied_{{ variable.name }}, self.{{ variable.name }}){% else %}{{ variable.name }}: setValueOptional(copied_{{ variable.name }}, self.{{ variable.name }}) ?? self.{{ variable.name }}{% endif %}{% if not forloop.last %}, {% endif %} 58 | {% endfor %} 59 | ) 60 | } 61 | } 62 | {% endfor %} 63 | // swiftlint:enable all 64 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import MiniSwiftTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += MiniSwiftTests.__allTests() 7 | 8 | XCTMain(tests) 9 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/ActionTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Mini 2 | @testable import MiniTasks 3 | import Nimble 4 | import XCTest 5 | 6 | final class ActionTests: XCTestCase { 7 | struct TasksTestEmptyAction: MiniTasks.EmptyAction { 8 | let task: AnyTask 9 | } 10 | 11 | func test_action_tag() { 12 | let action = SetCounterAction(counter: 1) 13 | 14 | XCTAssertEqual(String(describing: type(of: action)), SetCounterAction.tag) 15 | } 16 | 17 | func test_empty_action_fatal_error_initializer() { 18 | expect { 19 | _ = TasksTestEmptyAction(task: .success(), payload: nil) 20 | }.to(throwAssertion()) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/ChainTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Mini 2 | import XCTest 3 | 4 | final class ChainTests: XCTestCase { 5 | func test_forwarding_chain_forwards_action() { 6 | class TestAction: Action { 7 | var mutableProperty: Int 8 | 9 | init(property: Int) { 10 | mutableProperty = property 11 | } 12 | } 13 | 14 | let forwardingChain = ForwardingChain { action in 15 | guard let action = action as? TestAction else { fatalError() } 16 | action.mutableProperty = 1 17 | return action 18 | } 19 | 20 | let testAction = TestAction(property: 0) 21 | 22 | XCTAssert(testAction.mutableProperty == 0) 23 | 24 | let newAction = forwardingChain.proceed(testAction) as! TestAction 25 | 26 | XCTAssert(newAction.mutableProperty == 1) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/Common.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @testable import MiniPromises 3 | @testable import MiniTasks 4 | import RxSwift 5 | 6 | struct SetCounterAction: Action { 7 | let counter: Int 8 | } 9 | 10 | struct SetCounterActionLoaded: Action { 11 | let counter: Promise 12 | } 13 | 14 | struct SetCounterHashAction: Action { 15 | let counter: Int 16 | let key: String 17 | } 18 | 19 | struct SetCounterHashLoadedAction: MiniPromises.KeyedCompletableAction { 20 | typealias Key = String 21 | typealias Payload = Int 22 | 23 | let promise: [Key: Promise] 24 | } 25 | 26 | struct SetRawCounterAction: Action { 27 | let rawCounter: Int 28 | } 29 | 30 | struct SetRawCounterActionLoaded: MiniTasks.CompletableAction { 31 | typealias Payload = Int 32 | 33 | let task: AnyTask 34 | let payload: Payload? 35 | 36 | init(task: AnyTask, payload: Self.Payload?) { 37 | self.task = task 38 | self.payload = payload 39 | } 40 | } 41 | 42 | struct TestState: StateType { 43 | let counter: Promise 44 | let hashCounter: [String: Promise] 45 | let rawCounter: Int? 46 | let rawCounterTask: AnyTask 47 | 48 | init(counter: Promise = .idle(), 49 | hashCounter: [String: Promise] = [:], 50 | rawCounter: Int? = nil, 51 | rawCounterTask: AnyTask = .init()) { 52 | self.counter = counter 53 | self.hashCounter = hashCounter 54 | self.rawCounter = rawCounter 55 | self.rawCounterTask = rawCounterTask 56 | } 57 | 58 | public func isEqual(to other: StateType) -> Bool { 59 | guard let state = other as? TestState else { return false } 60 | guard counter == state.counter else { return false } 61 | guard hashCounter == state.hashCounter else { return false } 62 | guard rawCounter == state.rawCounter else { return false } 63 | guard rawCounterTask == state.rawCounterTask else { return false } 64 | return true 65 | } 66 | } 67 | 68 | extension TestState: Equatable { 69 | static func == (lhs: Self, rhs: Self) -> Bool { 70 | return lhs.isEqual(to: rhs) 71 | } 72 | } 73 | 74 | class TestStoreController: Disposable { 75 | let dispatcher: Dispatcher 76 | 77 | init(dispatcher: Dispatcher) { 78 | self.dispatcher = dispatcher 79 | } 80 | 81 | func counter(_ number: Int) { 82 | dispatcher.dispatch(SetCounterActionLoaded(counter: .value(number)), mode: .async) 83 | } 84 | 85 | func rawCounter(_ number: Int) { 86 | dispatcher.dispatch(SetRawCounterActionLoaded(task: .success(), payload: number), mode: .async) 87 | } 88 | 89 | func hashCounter(counter: Int, key: String) { 90 | dispatcher.dispatch(SetCounterHashLoadedAction(promise: [key: .value(counter)]), mode: .async) 91 | } 92 | 93 | public func dispose() { 94 | // NO-OP 95 | } 96 | } 97 | 98 | extension Store where State == TestState, StoreController == TestStoreController { 99 | var reducerGroup: ReducerGroup { 100 | return ReducerGroup( 101 | Reducer(of: SetCounterAction.self, on: dispatcher) { action in 102 | guard !self.state.counter.isPending else { return } 103 | self.state = TestState(counter: .pending()) 104 | self.storeController.counter(action.counter) 105 | }, 106 | Reducer(of: SetCounterActionLoaded.self, on: dispatcher) { action in 107 | self.state.counter 108 | .resolve(action.counter.result)? 109 | .notify(to: self) 110 | }, 111 | Reducer(of: SetCounterHashAction.self, on: dispatcher) { action in 112 | guard !(self.state.hashCounter[action.key]?.isPending ?? false) else { return } 113 | self.state = TestState(hashCounter: self.state.hashCounter.mergingNew(with: [action.key: .pending()])) 114 | self.storeController.hashCounter(counter: action.counter, key: action.key) 115 | }, 116 | Reducer(of: SetCounterHashLoadedAction.self, on: dispatcher) { action in 117 | self.state = TestState(hashCounter: self.state.hashCounter.resolve(with: action.promise)) 118 | }, 119 | Reducer(of: SetRawCounterAction.self, on: dispatcher) { _ in 120 | guard !self.state.rawCounterTask.isRunning else { return } 121 | self.state = TestState(rawCounterTask: .running()) 122 | self.storeController.rawCounter(1) 123 | }, 124 | Reducer(of: SetRawCounterActionLoaded.self, on: dispatcher) { action in 125 | guard self.state.rawCounterTask.isRunning else { return } 126 | self.state = TestState(rawCounter: action.payload, rawCounterTask: action.task) 127 | } 128 | ) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/DispatcherTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Mini 2 | import RxSwift 3 | @testable import TestMiddleware 4 | import XCTest 5 | 6 | final class DispatcherTests: XCTestCase { 7 | func test_subscription_count() { 8 | let dispatcher = Dispatcher() 9 | let disposable = CompositeDisposable() 10 | 11 | XCTAssert(dispatcher.subscriptionCount == 0) 12 | 13 | _ = disposable.insert(dispatcher.subscribe { (_: SetCounterActionLoaded) -> Void in }) 14 | _ = disposable.insert(dispatcher.subscribe { (_: SetCounterActionLoaded) -> Void in }) 15 | 16 | print(dispatcher.subscriptionCount) 17 | 18 | XCTAssert(dispatcher.subscriptionCount == 2) 19 | 20 | disposable.dispose() 21 | 22 | XCTAssert(dispatcher.subscriptionCount == 0) 23 | } 24 | 25 | func test_add_remove_middleware() { 26 | let dispatcher = Dispatcher() 27 | 28 | let middleware = TestMiddleware() 29 | 30 | dispatcher.add(middleware: middleware) 31 | 32 | dispatcher.dispatch(SetCounterActionLoaded(counter: .value(0)), mode: .sync) 33 | 34 | XCTAssert(middleware.actions(of: SetCounterActionLoaded.self).isEmpty == false) 35 | 36 | middleware.clear() 37 | 38 | XCTAssert(middleware.actions(of: SetCounterActionLoaded.self).isEmpty == true) 39 | 40 | dispatcher.remove(middleware: middleware) 41 | 42 | dispatcher.dispatch(SetCounterActionLoaded(counter: .value(0)), mode: .sync) 43 | 44 | XCTAssert(middleware.actions(of: SetCounterActionLoaded.self).isEmpty == true) 45 | } 46 | 47 | func test_add_remove_service() { 48 | class TestService: Service { 49 | var id: UUID = UUID() 50 | 51 | var actions = [Action]() 52 | 53 | private let expectation: XCTestExpectation 54 | 55 | init(_ expectation: XCTestExpectation) { 56 | self.expectation = expectation 57 | } 58 | 59 | var perform: ServiceChain { 60 | return { action, _ -> Void in 61 | self.actions.append(action) 62 | self.expectation.fulfill() 63 | } 64 | } 65 | } 66 | 67 | let expectation = XCTestExpectation(description: "Service") 68 | 69 | let dispatcher = Dispatcher() 70 | 71 | let service = TestService(expectation) 72 | 73 | dispatcher.register(service: service) 74 | 75 | XCTAssert(service.actions.isEmpty == true) 76 | 77 | dispatcher.dispatch(SetCounterActionLoaded(counter: .value(1)), mode: .sync) 78 | 79 | wait(for: [expectation], timeout: 5.0) 80 | 81 | XCTAssert(service.actions.count == 1) 82 | 83 | XCTAssert(service.actions.contains(where: { $0 is SetCounterActionLoaded }) == true) 84 | 85 | dispatcher.unregister(service: service) 86 | 87 | service.actions.removeAll() 88 | 89 | dispatcher.dispatch(SetCounterActionLoaded(counter: .value(1)), mode: .sync) 90 | 91 | XCTAssert(service.actions.isEmpty == true) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/PromiseTests.swift: -------------------------------------------------------------------------------- 1 | @testable import MiniPromises 2 | import Nimble 3 | import XCTest 4 | 5 | class PromiseTests: XCTestCase { 6 | fileprivate enum Error: Swift.Error { case dummy } 7 | 8 | func test_is_pending() { 9 | XCTAssertTrue(Promise.pending().isPending) 10 | XCTAssertFalse(Promise().isPending) 11 | XCTAssertFalse(Promise(error: Error.dummy).isPending) 12 | } 13 | 14 | func test_is_resolved() { 15 | XCTAssertFalse(Promise.pending().isResolved) 16 | XCTAssertTrue(Promise().isResolved) 17 | XCTAssertTrue(Promise(error: Error.dummy).isResolved) 18 | } 19 | 20 | func test_is_fulfilled() { 21 | XCTAssertFalse(Promise.pending().isFulfilled) 22 | XCTAssertTrue(Promise().isFulfilled) 23 | XCTAssertFalse(Promise(error: Error.dummy).isFulfilled) 24 | } 25 | 26 | func test_is_rejected() { 27 | XCTAssertFalse(Promise.pending().isRejected) 28 | XCTAssertTrue(Promise(error: Error.dummy).isRejected) 29 | XCTAssertFalse(Promise().isRejected) 30 | } 31 | 32 | func test_fulfill() { 33 | let promise: Promise = Promise() 34 | 35 | XCTAssertFalse(promise.isFulfilled) 36 | 37 | promise.fulfill(1) 38 | 39 | XCTAssertTrue(promise.value! == 1) 40 | 41 | XCTAssertTrue(promise.isFulfilled) 42 | XCTAssertFalse(promise.isRejected) 43 | XCTAssertTrue(promise.isResolved) 44 | XCTAssertFalse(promise.isPending) 45 | 46 | if case .failure? = promise.result { 47 | XCTFail() 48 | } 49 | } 50 | 51 | func test_reject() { 52 | let promise: Promise = Promise() 53 | 54 | XCTAssertFalse(promise.isFulfilled) 55 | 56 | promise.reject(Error.dummy) 57 | 58 | XCTAssertTrue(promise.isRejected) 59 | XCTAssertFalse(promise.isFulfilled) 60 | XCTAssertTrue(promise.isResolved) 61 | XCTAssertFalse(promise.isPending) 62 | 63 | if case .success? = promise.result { 64 | XCTFail() 65 | } 66 | } 67 | 68 | func test_immutability() { 69 | let promise: Promise = Promise() 70 | 71 | XCTAssertFalse(promise.isFulfilled) 72 | 73 | promise.fulfill(1) 74 | 75 | XCTAssertTrue(promise.value! == 1) 76 | 77 | XCTAssertTrue(promise.isFulfilled) 78 | XCTAssertFalse(promise.isRejected) 79 | XCTAssertTrue(promise.isResolved) 80 | XCTAssertFalse(promise.isPending) 81 | 82 | promise.fulfill(2) 83 | 84 | XCTAssertFalse(promise.value! == 2) 85 | 86 | XCTAssertTrue(promise.isCompleted) 87 | 88 | XCTAssertTrue(promise.error == nil) 89 | } 90 | 91 | func test_equality_with_value() { 92 | let promise1: Promise = .value(1) 93 | let promise2: Promise = .value(2) 94 | 95 | XCTAssertFalse(promise1 == promise2) 96 | } 97 | 98 | func test_equality_pending() { 99 | let promise1: Promise = .pending() 100 | let promise2: Promise = .pending() 101 | 102 | XCTAssertTrue(promise1 == promise2) 103 | } 104 | 105 | func test_equality_error() { 106 | let promise1: Promise = .init(error: Error.dummy) 107 | let promise2: Promise = .init(error: Error.dummy) 108 | 109 | XCTAssertTrue(promise1 == promise2) 110 | } 111 | 112 | func test_equality_completed() { 113 | let promise1: Promise = .empty() 114 | let promise2: Promise = .empty() 115 | 116 | let promise3 = promise1 117 | 118 | XCTAssertFalse(promise1 == promise2) 119 | XCTAssertTrue(promise1 == promise3) 120 | } 121 | 122 | func test_empty_resolution() { 123 | let promise: Promise = .empty() 124 | 125 | let resolution = promise.resolve(.success(())) 126 | 127 | XCTAssertNotNil(resolution) 128 | 129 | XCTAssertTrue(promise.isResolved) 130 | } 131 | 132 | func test_promise_properties() { 133 | let promise: Promise = .pending() 134 | promise(property: 1) 135 | let data: Int? = promise.property 136 | XCTAssertTrue(promise.property == data) 137 | XCTAssertNil(promise.not_a_property as Int?) 138 | } 139 | 140 | func test_dynamic_extension() { 141 | let promise: Promise = .pending() 142 | let date = Date() 143 | promise(date: date) 144 | let newDate: Date? = promise.date 145 | XCTAssertTrue(newDate == date) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/ReducerTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Mini 2 | import Nimble 3 | import RxSwift 4 | import XCTest 5 | 6 | final class ReducerTests: XCTestCase { 7 | func test_dispatcher_triggers_action_in_reducer_group_reducer() { 8 | let dBag = DisposeBag() 9 | let dispatcher = Dispatcher() 10 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 11 | store 12 | .reducerGroup 13 | .disposed(by: dBag) 14 | XCTAssertTrue(store.state.counter.isIdle) 15 | var counter: Int = 0 16 | store 17 | .map { $0.counter.value } 18 | .filter { $0 != nil } 19 | .subscribe(onNext: { _counter in 20 | counter = _counter! 21 | }) 22 | .disposed(by: dBag) 23 | dispatcher.dispatch( 24 | SetCounterAction(counter: 1), 25 | mode: .sync 26 | ) 27 | expect(counter).toEventually(equal(1), timeout: 5.5, pollInterval: 0.2) 28 | } 29 | 30 | func test_no_subscribe_to_store_produces_no_changes() { 31 | let dispatcher = Dispatcher() 32 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 33 | XCTAssertTrue(store.state.counter.isIdle) 34 | dispatcher.dispatch( 35 | SetCounterAction(counter: 2), 36 | mode: .sync 37 | ) 38 | XCTAssertTrue(store.state.counter.isIdle) 39 | } 40 | 41 | func test_subscribe_to_store_receive_actions() { 42 | let dBag = DisposeBag() 43 | let dispatcher = Dispatcher() 44 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 45 | XCTAssertTrue(store.state.counter.isIdle) 46 | var counter: Int = 0 47 | store 48 | .map { $0.counter.value } 49 | .filter { $0 != nil } 50 | .subscribe(onNext: { _counter in 51 | counter = _counter! 52 | }) 53 | .disposed(by: dBag) 54 | store 55 | .reducerGroup 56 | .disposed(by: dBag) 57 | dispatcher.dispatch( 58 | SetCounterAction(counter: 2), 59 | mode: .sync 60 | ) 61 | expect(counter).toEventually(equal(2), timeout: 5.5, pollInterval: 0.2) 62 | } 63 | 64 | func test_subscribe_to_store_receive_multiple_actions() { 65 | let dBag = DisposeBag() 66 | let dispatcher = Dispatcher() 67 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 68 | XCTAssertTrue(store.state.counter.isIdle) 69 | var counter: Int = 0 70 | store 71 | .map { $0.counter.value } 72 | .filter { $0 != nil } 73 | .subscribe(onNext: { _counter in 74 | counter = _counter! 75 | }) 76 | .disposed(by: dBag) 77 | store 78 | .reducerGroup 79 | .disposed(by: dBag) 80 | dispatcher.dispatch( 81 | SetCounterAction(counter: 2), 82 | mode: .sync 83 | ) 84 | expect(counter).toEventually(equal(2), timeout: 5.5, pollInterval: 0.2) 85 | dispatcher.dispatch( 86 | SetCounterAction(counter: 3), 87 | mode: .sync 88 | ) 89 | expect(counter).toEventually(equal(3), timeout: 5.5, pollInterval: 0.2) 90 | } 91 | 92 | func test_reset_state() { 93 | let dBag = DisposeBag() 94 | let dispatcher = Dispatcher() 95 | let initialState = TestState() 96 | let store = Store(initialState, dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 97 | XCTAssertTrue(store.state.counter.isIdle) 98 | var counter: Int = 0 99 | store 100 | .map { $0.counter.value } 101 | .filter { $0 != nil } 102 | .subscribe(onNext: { _counter in 103 | counter = _counter! 104 | }) 105 | .disposed(by: dBag) 106 | store 107 | .reducerGroup 108 | .disposed(by: dBag) 109 | dispatcher.dispatch( 110 | SetCounterAction(counter: 3), 111 | mode: .sync 112 | ) 113 | expect(counter).toEventually(equal(3), timeout: 5.5, pollInterval: 0.2) 114 | store.reset() 115 | XCTAssert(store.state.isEqual(to: initialState)) 116 | } 117 | 118 | func test_state_received_in_store() throws { 119 | let dBag = DisposeBag() 120 | let dispatcher = Dispatcher() 121 | let initialState = TestState() 122 | let store = Store(initialState, dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 123 | XCTAssertTrue(store.state.counter.isIdle) 124 | var counter: Int = 0 125 | store 126 | .map { $0.counter.value } 127 | .filter { $0 != nil } 128 | .subscribe(onNext: { _counter in 129 | counter = _counter! 130 | }) 131 | .disposed(by: dBag) 132 | store 133 | .reducerGroup 134 | .disposed(by: dBag) 135 | dispatcher.dispatch( 136 | SetCounterAction(counter: 3), 137 | mode: .sync 138 | ) 139 | expect(counter).toEventually(equal(3), timeout: 5.5, pollInterval: 0.2) 140 | dispatcher.dispatch( 141 | SetCounterAction(counter: 4), 142 | mode: .sync 143 | ) 144 | expect(counter).toEventually(equal(4), timeout: 5.5, pollInterval: 0.2) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/RxTests/ObservableTypeTests.swift: -------------------------------------------------------------------------------- 1 | @testable import MiniPromises 2 | import Nimble 3 | import RxBlocking 4 | import RxSwift 5 | import RxTest 6 | import XCTest 7 | 8 | private struct TestFilterKeyPath: Equatable { 9 | let number: Int 10 | let done: Bool 11 | } 12 | 13 | private func matchPromiseHash(_ by: [K: Promise]) -> Predicate<[K: Promise]> { 14 | return Predicate { expression in 15 | guard let dict = try expression.evaluate() else { 16 | return PredicateResult(status: .fail, 17 | message: .fail("failed evaluating expression")) 18 | } 19 | guard dict == by else { 20 | return PredicateResult(status: .fail, 21 | message: .fail("Dictionary doesn't match")) 22 | } 23 | return PredicateResult(status: .matches, 24 | message: .expectedTo("expectation fulfilled")) 25 | } 26 | } 27 | 28 | final class ObservableTypeTests: XCTestCase { 29 | var scheduler: TestScheduler! 30 | var disposeBag: DisposeBag! 31 | 32 | override func setUp() { 33 | scheduler = TestScheduler(initialClock: 0) 34 | disposeBag = DisposeBag() 35 | } 36 | 37 | func test_filter_one() { 38 | let filterOneObserver = scheduler.createObserver(Int.self) 39 | 40 | scheduler.createColdObservable( 41 | [ 42 | .next(10, 10), 43 | .next(20, 20), 44 | .next(30, 30), 45 | .completed(40), 46 | ] 47 | ) 48 | .filterOne { $0 == 20 } 49 | .subscribe(filterOneObserver) 50 | .disposed(by: disposeBag) 51 | 52 | scheduler.start() 53 | 54 | XCTAssertEqual(filterOneObserver.events, [ 55 | .next(20, 20), 56 | .completed(20), 57 | ]) 58 | } 59 | 60 | func test_filter_keypath() { 61 | let filterKeyPathObserver = scheduler.createObserver(TestFilterKeyPath.self) 62 | 63 | scheduler.createColdObservable( 64 | [ 65 | .next(10, TestFilterKeyPath(number: 0, done: false)), 66 | .next(20, TestFilterKeyPath(number: 1, done: true)), 67 | .completed(40), 68 | ] 69 | ) 70 | .filter(\TestFilterKeyPath.done) 71 | .subscribe(filterKeyPathObserver) 72 | .disposed(by: disposeBag) 73 | 74 | scheduler.start() 75 | 76 | XCTAssertEqual(filterKeyPathObserver.events, [ 77 | .next(20, TestFilterKeyPath(number: 1, done: true)), 78 | .completed(40), 79 | ]) 80 | } 81 | 82 | func test_skipping_next() { 83 | let skippingNextObserver = scheduler.createObserver(Int.self) 84 | 85 | scheduler.createColdObservable( 86 | [ 87 | .next(10, 10), 88 | .next(20, 20), 89 | .next(30, 30), 90 | .completed(40), 91 | ] 92 | ) 93 | .skippingCurrent() 94 | .subscribe(skippingNextObserver) 95 | .disposed(by: disposeBag) 96 | 97 | scheduler.start() 98 | 99 | XCTAssertEqual(skippingNextObserver.events, [ 100 | .next(20, 20), 101 | .next(30, 30), 102 | .completed(40), 103 | ]) 104 | } 105 | 106 | func test_dispatch_action_from_store() throws { 107 | let dispatcher = Dispatcher() 108 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 109 | 110 | store 111 | .reducerGroup 112 | .disposed(by: disposeBag) 113 | 114 | guard let state = try Observable> 115 | .dispatch(using: dispatcher, 116 | factory: SetCounterAction(counter: 1), 117 | taskMap: { $0.counter }, 118 | on: store) 119 | .toBlocking(timeout: 5.0).first() 120 | else { 121 | fatalError() 122 | } 123 | 124 | XCTAssertTrue(state.counter.isResolved) 125 | XCTAssertTrue(state.counter.error == nil) 126 | XCTAssertEqual(state.counter.value, 1) 127 | } 128 | 129 | func test_dispatch_hashable_action_from_store() throws { 130 | let dispatcher = Dispatcher() 131 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 132 | 133 | store 134 | .reducerGroup 135 | .disposed(by: disposeBag) 136 | 137 | guard let state = try Observable> 138 | .dispatch(using: dispatcher, 139 | factory: SetCounterHashAction(counter: 1, key: "hello"), 140 | key: "hello", 141 | taskMap: { $0.hashCounter }, 142 | on: store) 143 | .toBlocking(timeout: 5.0).first() 144 | else { 145 | fatalError() 146 | } 147 | 148 | XCTAssertTrue(state.hashCounter[promise: "hello"].isResolved) 149 | XCTAssertTrue(state.hashCounter[promise: "hello"].error == nil) 150 | expect(state.hashCounter).to(matchPromiseHash(["hello": Promise.value(1)])) 151 | } 152 | 153 | func test_with_state_changes_promise() throws { 154 | let dispatcher = Dispatcher() 155 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 156 | 157 | store 158 | .reducerGroup 159 | .disposed(by: disposeBag) 160 | 161 | guard let counter = try store 162 | .dispatch(SetCounterAction(counter: 1)) 163 | .withStateChanges(in: \.counter) 164 | .skippingCurrent() 165 | .toBlocking(timeout: 5.0).first() else { 166 | fatalError() 167 | } 168 | 169 | XCTAssertTrue(counter.isFulfilled) 170 | XCTAssertTrue(counter.value == 1) 171 | } 172 | 173 | func test_with_state_changes_standalone() throws { 174 | let dispatcher = Dispatcher() 175 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 176 | 177 | store 178 | .reducerGroup 179 | .disposed(by: disposeBag) 180 | 181 | dispatcher.dispatch(SetCounterAction(counter: 1), mode: .sync) 182 | 183 | guard let counter = try store 184 | .withStateChanges(in: \.counter) 185 | .skippingCurrent() 186 | .toBlocking(timeout: 5.0).first() else { 187 | fatalError() 188 | } 189 | 190 | XCTAssertTrue(counter.isFulfilled) 191 | XCTAssertTrue(counter.value == 1) 192 | } 193 | 194 | func test_select() throws { 195 | let dispatcher = Dispatcher() 196 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 197 | 198 | store 199 | .reducerGroup 200 | .disposed(by: disposeBag) 201 | 202 | dispatcher.dispatch(SetRawCounterAction(rawCounter: 1), mode: .sync) 203 | 204 | guard let counter = try store 205 | .select(\.rawCounter) 206 | .toBlocking(timeout: 5.0).first() else { 207 | fatalError() 208 | } 209 | 210 | XCTAssertEqual(counter, 1) 211 | } 212 | 213 | func test_with_state_changes_task() throws { 214 | let dispatcher = Dispatcher() 215 | let store = Store(TestState(), dispatcher: dispatcher, storeController: TestStoreController(dispatcher: dispatcher)) 216 | 217 | store 218 | .reducerGroup 219 | .disposed(by: disposeBag) 220 | 221 | dispatcher.dispatch(SetRawCounterAction(rawCounter: 1), mode: .sync) 222 | 223 | guard let counter = try store 224 | .withStateChanges(in: \.rawCounter, that: \.rawCounterTask) 225 | .toBlocking(timeout: 5.0).first() else { 226 | fatalError() 227 | } 228 | 229 | XCTAssertEqual(counter, 1) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/TaskTests.swift: -------------------------------------------------------------------------------- 1 | @testable import MiniTasks 2 | import XCTest 3 | 4 | class TaskTests: XCTestCase { 5 | let error = NSError(domain: #function, code: -1, userInfo: nil) 6 | 7 | func test_check_states_for_idle_task() { 8 | let task = AnyTask.idle() 9 | 10 | XCTAssertEqual(task.status, .idle) 11 | XCTAssertNil(task.error) 12 | 13 | XCTAssertFalse(task.isRunning) 14 | XCTAssertFalse(task.isFailure) 15 | XCTAssertFalse(task.isCompleted) 16 | XCTAssertFalse(task.isSuccessful) 17 | } 18 | 19 | func test_check_states_for_running_task() { 20 | let task = AnyTask.running() 21 | 22 | XCTAssertEqual(task.status, .running) 23 | XCTAssertNil(task.error) 24 | 25 | XCTAssertTrue(task.isRunning) 26 | XCTAssertFalse(task.isFailure) 27 | XCTAssertFalse(task.isCompleted) 28 | XCTAssertFalse(task.isSuccessful) 29 | } 30 | 31 | func test_check_states_for_success_task() { 32 | let task = AnyTask.success() 33 | 34 | XCTAssertEqual(task.status, .success) 35 | XCTAssertNil(task.error) 36 | 37 | XCTAssertFalse(task.isRunning) 38 | XCTAssertFalse(task.isFailure) 39 | XCTAssertTrue(task.isCompleted) 40 | XCTAssertTrue(task.isSuccessful) 41 | } 42 | 43 | func test_check_states_for_success_typed_task() { 44 | let task: TypedTask = AnyTask.success(1) 45 | 46 | XCTAssertEqual(task.status, .success) 47 | XCTAssertNil(task.error) 48 | 49 | XCTAssertFalse(task.isRunning) 50 | XCTAssertFalse(task.isFailure) 51 | XCTAssertTrue(task.isCompleted) 52 | XCTAssertTrue(task.isSuccessful) 53 | XCTAssertEqual(task.data, 1) 54 | } 55 | 56 | func test_check_states_for_failure_task() { 57 | let task = AnyTask.failure(error) 58 | 59 | XCTAssertEqual(task.status, .error) 60 | XCTAssertEqual(task.error as NSError?, error) 61 | 62 | XCTAssertFalse(task.isRunning) 63 | XCTAssertTrue(task.isFailure) 64 | XCTAssertTrue(task.isCompleted) 65 | XCTAssertFalse(task.isSuccessful) 66 | } 67 | 68 | func test_task_properties() { 69 | let task: AnyTask = .idle() 70 | let date = Date() 71 | task.date = date 72 | 73 | XCTAssertEqual(task.date as Date?, date) 74 | XCTAssertNil(task.not_a_property as Int?) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/Utils/Foundation/Dictionary+ExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Mini 2 | import XCTest 3 | 4 | final class DictionaryExtensionsTests: XCTestCase { 5 | func test_get_or_put() { 6 | var dic = [String: Int]() 7 | 8 | XCTAssertEqual(dic.getOrPut("foo", defaultValue: 1), 1) 9 | XCTAssertEqual(["foo": 1], dic) 10 | 11 | dic["bar"] = 2 12 | 13 | XCTAssertEqual(dic.getOrPut("bar", defaultValue: Int.max), 2) 14 | } 15 | 16 | func test_unrapping_subscript() { 17 | var dic = [String: Int]() 18 | 19 | let test: Int? = dic[unwrapping: "foo"] 20 | 21 | XCTAssertEqual(test, nil) 22 | 23 | dic["foo"] = 1 24 | 25 | let test2: Int? = dic[unwrapping: "foo"] 26 | 27 | XCTAssertEqual(test2, 1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Tests/MiniSwiftTests/Utils/Foundation/DispatchQueueTests.swift: -------------------------------------------------------------------------------- 1 | @testable import Mini 2 | import Nimble 3 | import XCTest 4 | 5 | final class DispatchQueueTests: XCTestCase { 6 | func test_main_queue() { 7 | var isMain: Bool = false 8 | 9 | DispatchQueue.main.async { 10 | isMain = DispatchQueue.isMain 11 | } 12 | 13 | expect(isMain).toEventually(beTrue()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /bin/pre-commit.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | raise 'Test failed' unless system('swift test') 4 | raise 'Generate linuxmain failed' unless system('swift test --generate-linuxmain') 5 | raise 'Swiftformat failed' unless system('swift run swiftformat --swiftversion 5.0 .') 6 | raise 'Swiftlint failed' unless system('swift run swiftlint autocorrect') 7 | raise 'Git add failed' unless system('git add .') 8 | -------------------------------------------------------------------------------- /bin/pre-push.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | `swift test` 4 | raise 'Test failed' unless $?.success? 5 | -------------------------------------------------------------------------------- /docs/Mini/README.md: -------------------------------------------------------------------------------- 1 | ## Protocols 2 | 3 | - [Action](protocols/Action.md) 4 | - [Chain](protocols/Chain.md) 5 | - [Group](protocols/Group.md) 6 | - [Middleware](protocols/Middleware.md) 7 | - [OptionalType](protocols/OptionalType.md) 8 | - [Service](protocols/Service.md) 9 | - [StateType](protocols/StateType.md) 10 | - [StoreType](protocols/StoreType.md) 11 | 12 | ## Structs 13 | 14 | - [DispatchMode](structs/DispatchMode.md) 15 | 16 | ## Classes 17 | 18 | - [Dispatcher](classes/Dispatcher.md) 19 | - [DispatcherSubscription](classes/DispatcherSubscription.md) 20 | - [ForwardingChain](classes/ForwardingChain.md) 21 | - [OrderedSet](classes/OrderedSet.md) 22 | - [Reducer](classes/Reducer.md) 23 | - [ReducerGroup](classes/ReducerGroup.md) 24 | - [RootChain](classes/RootChain.md) 25 | - [SharedDictionary](classes/SharedDictionary.md) 26 | - [Store](classes/Store.md) 27 | 28 | ## Enums 29 | 30 | - [UI](enums/UI.md) 31 | 32 | ## Extensions 33 | 34 | - [Action](extensions/Action.md) 35 | - [Dictionary](extensions/Dictionary.md) 36 | - [ObservableType](extensions/ObservableType.md) 37 | - [Optional](extensions/Optional.md) 38 | - [StateType](extensions/StateType.md) 39 | - [Store](extensions/Store.md) 40 | - [StoreType](extensions/StoreType.md) 41 | 42 | ## Methods 43 | 44 | - [^(_:)](methods/^(_:).md) 45 | 46 | # Reference Documentation 47 | This reference documentation was generated with 48 | [SourceDocs](https://github.com/eneko/SourceDocs). -------------------------------------------------------------------------------- /docs/Mini/classes/Dispatcher.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `Dispatcher` 4 | 5 | ```swift 6 | public final class Dispatcher 7 | ``` 8 | 9 | ## Properties 10 | ### `subscriptionCount` 11 | 12 | ```swift 13 | public var subscriptionCount: Int 14 | ``` 15 | 16 | ## Methods 17 | ### `init()` 18 | 19 | ```swift 20 | public init() 21 | ``` 22 | 23 | ### `add(middleware:)` 24 | 25 | ```swift 26 | public func add(middleware: Middleware) 27 | ``` 28 | 29 | ### `remove(middleware:)` 30 | 31 | ```swift 32 | public func remove(middleware: Middleware) 33 | ``` 34 | 35 | ### `register(service:)` 36 | 37 | ```swift 38 | public func register(service: Service) 39 | ``` 40 | 41 | ### `unregister(service:)` 42 | 43 | ```swift 44 | public func unregister(service: Service) 45 | ``` 46 | 47 | ### `subscribe(priority:tag:completion:)` 48 | 49 | ```swift 50 | public func subscribe(priority: Int, tag: String, completion: @escaping (Action) -> Void) -> DispatcherSubscription 51 | ``` 52 | 53 | ### `registerInternal(subscription:)` 54 | 55 | ```swift 56 | public func registerInternal(subscription: DispatcherSubscription) -> DispatcherSubscription 57 | ``` 58 | 59 | ### `unregisterInternal(subscription:)` 60 | 61 | ```swift 62 | public func unregisterInternal(subscription: DispatcherSubscription) 63 | ``` 64 | 65 | ### `subscribe(completion:)` 66 | 67 | ```swift 68 | public func subscribe(completion: @escaping (T) -> Void) -> DispatcherSubscription 69 | ``` 70 | 71 | ### `subscribe(tag:completion:)` 72 | 73 | ```swift 74 | public func subscribe(tag: String, completion: @escaping (T) -> Void) -> DispatcherSubscription 75 | ``` 76 | 77 | ### `subscribe(tag:completion:)` 78 | 79 | ```swift 80 | public func subscribe(tag: String, completion: @escaping (Action) -> Void) -> DispatcherSubscription 81 | ``` 82 | 83 | ### `dispatch(_:mode:)` 84 | 85 | ```swift 86 | public func dispatch(_ action: Action, mode: Dispatcher.DispatchMode.UI) 87 | ``` 88 | -------------------------------------------------------------------------------- /docs/Mini/classes/DispatcherSubscription.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `DispatcherSubscription` 4 | 5 | ```swift 6 | public final class DispatcherSubscription: Comparable, Disposable 7 | ``` 8 | 9 | ## Properties 10 | ### `id` 11 | 12 | ```swift 13 | public let id: Int 14 | ``` 15 | 16 | ### `tag` 17 | 18 | ```swift 19 | public let tag: String 20 | ``` 21 | 22 | ## Methods 23 | ### `init(dispatcher:id:priority:tag:completion:)` 24 | 25 | ```swift 26 | public init(dispatcher: Dispatcher, 27 | id: Int, 28 | priority: Int, 29 | tag: String, 30 | completion: @escaping (Action) -> Void) 31 | ``` 32 | 33 | ### `dispose()` 34 | 35 | ```swift 36 | public func dispose() 37 | ``` 38 | 39 | ### `on(_:)` 40 | 41 | ```swift 42 | public func on(_ action: Action) 43 | ``` 44 | 45 | ### `==(_:_:)` 46 | 47 | ```swift 48 | public static func == (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool 49 | ``` 50 | 51 | #### Parameters 52 | 53 | | Name | Description | 54 | | ---- | ----------- | 55 | | lhs | A value to compare. | 56 | | rhs | Another value to compare. | 57 | 58 | ### `>(_:_:)` 59 | 60 | ```swift 61 | public static func > (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool 62 | ``` 63 | 64 | #### Parameters 65 | 66 | | Name | Description | 67 | | ---- | ----------- | 68 | | lhs | A value to compare. | 69 | | rhs | Another value to compare. | 70 | 71 | ### `<(_:_:)` 72 | 73 | ```swift 74 | public static func < (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool 75 | ``` 76 | 77 | #### Parameters 78 | 79 | | Name | Description | 80 | | ---- | ----------- | 81 | | lhs | A value to compare. | 82 | | rhs | Another value to compare. | 83 | 84 | ### `>=(_:_:)` 85 | 86 | ```swift 87 | public static func >= (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool 88 | ``` 89 | 90 | #### Parameters 91 | 92 | | Name | Description | 93 | | ---- | ----------- | 94 | | lhs | A value to compare. | 95 | | rhs | Another value to compare. | 96 | 97 | ### `<=(_:_:)` 98 | 99 | ```swift 100 | public static func <= (lhs: DispatcherSubscription, rhs: DispatcherSubscription) -> Bool 101 | ``` 102 | 103 | #### Parameters 104 | 105 | | Name | Description | 106 | | ---- | ----------- | 107 | | lhs | A value to compare. | 108 | | rhs | Another value to compare. | -------------------------------------------------------------------------------- /docs/Mini/classes/ForwardingChain.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `ForwardingChain` 4 | 5 | ```swift 6 | public final class ForwardingChain: Chain 7 | ``` 8 | 9 | ## Properties 10 | ### `proceed` 11 | 12 | ```swift 13 | public var proceed: Next 14 | ``` 15 | 16 | ## Methods 17 | ### `init(next:)` 18 | 19 | ```swift 20 | public init(next: @escaping Next) 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/Mini/classes/OrderedSet.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `OrderedSet` 4 | 5 | ```swift 6 | public class OrderedSet 7 | ``` 8 | 9 | > An Ordered Set is a collection where all items in the set follow an ordering, 10 | > usually ordered from 'least' to 'most'. The way you value and compare items 11 | > can be user-defined. 12 | 13 | ## Properties 14 | ### `count` 15 | 16 | ```swift 17 | public var count: Int 18 | ``` 19 | 20 | > Returns the number of elements in the OrderedSet. 21 | 22 | ### `max` 23 | 24 | ```swift 25 | public var max: T? 26 | ``` 27 | 28 | > Returns the 'maximum' or 'largest' value in the set. 29 | 30 | ### `min` 31 | 32 | ```swift 33 | public var min: T? 34 | ``` 35 | 36 | > Returns the 'minimum' or 'smallest' value in the set. 37 | 38 | ## Methods 39 | ### `init(initial:)` 40 | 41 | ```swift 42 | public init(initial: [T] = []) 43 | ``` 44 | 45 | ### `insert(_:)` 46 | 47 | ```swift 48 | public func insert(_ item: T) -> Bool 49 | ``` 50 | 51 | > Inserts an item. Performance: O(n) 52 | 53 | ### `insert(_:)` 54 | 55 | ```swift 56 | public func insert(_ items: [T]) -> Bool 57 | ``` 58 | 59 | > Insert an array of items 60 | 61 | ### `remove(_:)` 62 | 63 | ```swift 64 | public func remove(_ item: T) -> Bool 65 | ``` 66 | 67 | > Removes an item if it exists. Performance: O(n) 68 | 69 | ### `exists(_:)` 70 | 71 | ```swift 72 | public func exists(_ item: T) -> Bool 73 | ``` 74 | 75 | > Returns true if and only if the item exists somewhere in the set. 76 | 77 | ### `indexOf(_:)` 78 | 79 | ```swift 80 | public func indexOf(_ item: T) -> Int? 81 | ``` 82 | 83 | > Returns the index of an item if it exists, or nil otherwise. 84 | 85 | ### `kLargest(element:)` 86 | 87 | ```swift 88 | public func kLargest(element: Int) -> T? 89 | ``` 90 | 91 | > Returns the k-th largest element in the set, if k is in the range 92 | > [1, count]. Returns nil otherwise. 93 | 94 | ### `kSmallest(element:)` 95 | 96 | ```swift 97 | public func kSmallest(element: Int) -> T? 98 | ``` 99 | 100 | > Returns the k-th smallest element in the set, if k is in the range 101 | > [1, count]. Returns nil otherwise. 102 | 103 | ### `forEach(_:)` 104 | 105 | ```swift 106 | public func forEach(_ body: (T) -> Swift.Void) 107 | ``` 108 | 109 | > For each function 110 | 111 | ### `enumerated()` 112 | 113 | ```swift 114 | public func enumerated() -> EnumeratedSequence<[T]> 115 | ``` 116 | 117 | > Enumerated function 118 | -------------------------------------------------------------------------------- /docs/Mini/classes/Reducer.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `Reducer` 4 | 5 | ```swift 6 | public class Reducer: Disposable 7 | ``` 8 | 9 | > The `Reducer` defines the behavior to be executed when a certain 10 | > `Action` object is received. 11 | 12 | ## Properties 13 | ### `action` 14 | 15 | ```swift 16 | public let action: A.Type 17 | ``` 18 | 19 | > The `Action` type which the `Reducer` listens to. 20 | 21 | ### `dispatcher` 22 | 23 | ```swift 24 | public let dispatcher: Dispatcher 25 | ``` 26 | 27 | > The `Dispatcher` object that sends the `Action` objects. 28 | 29 | ### `reducer` 30 | 31 | ```swift 32 | public let reducer: (A) -> Void 33 | ``` 34 | 35 | > The behavior to be executed when the `Dispatcher` sends a certain `Action` 36 | 37 | ## Methods 38 | ### `init(of:on:reducer:)` 39 | 40 | ```swift 41 | public init(of action: A.Type, on dispatcher: Dispatcher, reducer: @escaping (A) -> Void) 42 | ``` 43 | 44 | > Initializes a new `Reducer` object. 45 | > - Parameter action: The `Action` type that will be listened to. 46 | > - Parameter dispatcher: The `Dispatcher` that sends the `Action`. 47 | > - Parameter reducer: The closure that will be executed when the `Dispatcher` 48 | > sends the defined `Action` type. 49 | 50 | #### Parameters 51 | 52 | | Name | Description | 53 | | ---- | ----------- | 54 | | action | The `Action` type that will be listened to. | 55 | | dispatcher | The `Dispatcher` that sends the `Action`. | 56 | | reducer | The closure that will be executed when the `Dispatcher` sends the defined `Action` type. | 57 | 58 | ### `dispose()` 59 | 60 | ```swift 61 | public func dispose() 62 | ``` 63 | 64 | > Dispose resource. 65 | -------------------------------------------------------------------------------- /docs/Mini/classes/ReducerGroup.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `ReducerGroup` 4 | 5 | ```swift 6 | public class ReducerGroup: Group 7 | ``` 8 | 9 | ## Properties 10 | ### `disposeBag` 11 | 12 | ```swift 13 | public let disposeBag = CompositeDisposable() 14 | ``` 15 | 16 | ## Methods 17 | ### `init(_:)` 18 | 19 | ```swift 20 | public init(_ builder: Disposable...) 21 | ``` 22 | 23 | ### `dispose()` 24 | 25 | ```swift 26 | public func dispose() 27 | ``` 28 | -------------------------------------------------------------------------------- /docs/Mini/classes/RootChain.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `RootChain` 4 | 5 | ```swift 6 | public final class RootChain: Chain 7 | ``` 8 | 9 | ## Properties 10 | ### `proceed` 11 | 12 | ```swift 13 | public var proceed: Next 14 | ``` 15 | 16 | ## Methods 17 | ### `init(map:)` 18 | 19 | ```swift 20 | public init(map: SubscriptionMap) 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/Mini/classes/SharedDictionary.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `SharedDictionary` 4 | 5 | ```swift 6 | public class SharedDictionary 7 | ``` 8 | 9 | > Wrapper class to allow pass dictionaries with a memory reference 10 | 11 | ## Properties 12 | ### `innerDictionary` 13 | 14 | ```swift 15 | public var innerDictionary: [Key: Value] 16 | ``` 17 | 18 | ## Methods 19 | ### `init()` 20 | 21 | ```swift 22 | public init() 23 | ``` 24 | 25 | ### `getOrPut(_:defaultValue:)` 26 | 27 | ```swift 28 | public func getOrPut(_ key: Key, defaultValue: @autoclosure () -> Value) -> Value 29 | ``` 30 | 31 | ### `get(withKey:)` 32 | 33 | ```swift 34 | public func get(withKey key: Key) -> Value? 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/Mini/classes/Store.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `Store` 4 | 5 | ```swift 6 | public class Store: ObservableType, StoreType 7 | ``` 8 | 9 | ## Properties 10 | ### `objectWillChange` 11 | 12 | ```swift 13 | public var objectWillChange: ObjectWillChangePublisher 14 | ``` 15 | 16 | ### `dispatcher` 17 | 18 | ```swift 19 | public let dispatcher: Dispatcher 20 | ``` 21 | 22 | ### `storeController` 23 | 24 | ```swift 25 | public var storeController: StoreController 26 | ``` 27 | 28 | ### `state` 29 | 30 | ```swift 31 | public var state: State 32 | ``` 33 | 34 | ### `initialState` 35 | 36 | ```swift 37 | public var initialState: State 38 | ``` 39 | 40 | ### `reducerGroup` 41 | 42 | ```swift 43 | public var reducerGroup: ReducerGroup 44 | ``` 45 | 46 | ## Methods 47 | ### `init(_:dispatcher:storeController:)` 48 | 49 | ```swift 50 | public init(_ state: State, 51 | dispatcher: Dispatcher, 52 | storeController: StoreController) 53 | ``` 54 | 55 | ### `notify()` 56 | 57 | ```swift 58 | public func notify() 59 | ``` 60 | 61 | ### `replayOnce()` 62 | 63 | ```swift 64 | public func replayOnce() 65 | ``` 66 | 67 | ### `reset()` 68 | 69 | ```swift 70 | public func reset() 71 | ``` 72 | 73 | ### `subscribe(_:)` 74 | 75 | ```swift 76 | public func subscribe(_ observer: Observer) -> Disposable where Observer.Element == Store.Element 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/Mini/enums/UI.md: -------------------------------------------------------------------------------- 1 | **ENUM** 2 | 3 | # `UI` 4 | 5 | ```swift 6 | public enum UI 7 | ``` 8 | 9 | ## Cases 10 | ### `sync` 11 | 12 | ```swift 13 | case sync, async 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/Mini/extensions/Action.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `Action` 4 | 5 | ## Properties 6 | ### `innerTag` 7 | 8 | ```swift 9 | public var innerTag: String 10 | ``` 11 | 12 | > String used as tag of the given Action based on his name. 13 | > - Returns: The name of the action as a String. 14 | -------------------------------------------------------------------------------- /docs/Mini/extensions/Dictionary.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `Dictionary` 4 | 5 | ## Methods 6 | ### `getOrPut(_:defaultValue:)` 7 | 8 | ```swift 9 | public mutating func getOrPut(_ key: Key, defaultValue: @autoclosure () -> Value) -> Value 10 | ``` 11 | 12 | > Returns the value for the given key. If the key is not found in the map, calls the `defaultValue` function, 13 | > puts its result into the map under the given key and returns it. 14 | -------------------------------------------------------------------------------- /docs/Mini/extensions/ObservableType.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `ObservableType` 4 | 5 | ## Methods 6 | ### `filterOne(_:)` 7 | 8 | ```swift 9 | public func filterOne(_ condition: @escaping (Element) -> Bool) -> Observable 10 | ``` 11 | 12 | > Take the first element that matches the filter function. 13 | > 14 | > - Parameter fn: Filter closure. 15 | > - Returns: The first element that matches the filter. 16 | 17 | #### Parameters 18 | 19 | | Name | Description | 20 | | ---- | ----------- | 21 | | fn | Filter closure. | 22 | 23 | ### `filter(_:)` 24 | 25 | ```swift 26 | public func filter(_ keyPath: KeyPath) -> Observable 27 | ``` 28 | 29 | ### `map(_:)` 30 | 31 | ```swift 32 | public func map(_ keyPath: KeyPath) -> Observable 33 | ``` 34 | 35 | ### `one()` 36 | 37 | ```swift 38 | public func one() -> Observable 39 | ``` 40 | 41 | ### `skippingCurrent()` 42 | 43 | ```swift 44 | public func skippingCurrent() -> Observable 45 | ``` 46 | 47 | ### `select(_:)` 48 | 49 | ```swift 50 | public func select(_ keyPath: KeyPath) -> Observable where T.Wrapped: Equatable 51 | ``` 52 | 53 | > Selects a property component from an `Element` filtering `nil` and emitting only distinct contiguous elements. 54 | 55 | ### `filterNil()` 56 | 57 | > Unwraps and filters out `nil` elements. 58 | > - returns: `Observable` of source `Observable`'s elements, with `nil` elements filtered out. 59 | 60 | ### `withStateChanges(in:that:)` 61 | 62 | ```swift 63 | public func withStateChanges(in stateComponent: KeyPath, that componentProperty: KeyPath) -> Observable 64 | ``` 65 | 66 | > Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes. 67 | -------------------------------------------------------------------------------- /docs/Mini/extensions/Optional.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `Optional` 4 | 5 | ## Properties 6 | ### `value` 7 | 8 | > Cast `Optional` to `Wrapped?` 9 | -------------------------------------------------------------------------------- /docs/Mini/extensions/StateType.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `StateType` 4 | 5 | ## Methods 6 | ### `isEqual(to:)` 7 | 8 | ```swift 9 | func isEqual(to other: StateType) -> Bool 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/Mini/extensions/Store.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `Store` 4 | 5 | ## Methods 6 | ### `replaying()` 7 | 8 | ```swift 9 | func replaying() -> Observable 10 | ``` 11 | 12 | ### `dispatch(_:)` 13 | 14 | ```swift 15 | public func dispatch(_ action: @autoclosure @escaping () -> A) -> Observable 16 | ``` 17 | 18 | ### `withStateChanges(in:)` 19 | 20 | ```swift 21 | public func withStateChanges(in stateComponent: KeyPath) -> Observable 22 | ``` 23 | -------------------------------------------------------------------------------- /docs/Mini/extensions/StoreType.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `StoreType` 4 | 5 | ## Properties 6 | ### `reducerGroup` 7 | 8 | ```swift 9 | public var reducerGroup: ReducerGroup 10 | ``` 11 | 12 | > Property responsible of reduce the `State` given a certain `Action` being triggered. 13 | > ``` 14 | > public var reducerGroup: ReducerGroup { 15 | > ReducerGroup {[ 16 | > Reducer(of: SomeAction.self, on: self.dispatcher) { (action: SomeAction) 17 | > self.state = myCoolNewState 18 | > }, 19 | > Reducer(of: OtherAction.self, on: self.dispatcher) { (action: OtherAction) 20 | > // Needed work 21 | > self.state = myAnotherState 22 | > } 23 | > } 24 | > ]} 25 | > ``` 26 | > - Note : The property has a default implementation which complies with the @_functionBuilder's current limitations, where no empty blocks can be produced in this iteration. 27 | -------------------------------------------------------------------------------- /docs/Mini/methods/^(_:).md: -------------------------------------------------------------------------------- 1 | ### `^(_:)` 2 | 3 | ```swift 4 | public prefix func ^ ( 5 | _ keypath: KeyPath 6 | ) -> (Root) -> Value 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/Mini/protocols/Action.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `Action` 4 | 5 | ```swift 6 | public protocol Action 7 | ``` 8 | 9 | > Protocol that has to be conformed by any object that can be dispatched 10 | > by a `Dispatcher` object. 11 | -------------------------------------------------------------------------------- /docs/Mini/protocols/Chain.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `Chain` 4 | 5 | ```swift 6 | public protocol Chain 7 | ``` 8 | 9 | ## Properties 10 | ### `proceed` 11 | 12 | ```swift 13 | var proceed: Next 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/Mini/protocols/Group.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `Group` 4 | 5 | ```swift 6 | public protocol Group: Disposable 7 | ``` 8 | 9 | ## Properties 10 | ### `disposeBag` 11 | 12 | ```swift 13 | var disposeBag: CompositeDisposable 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/Mini/protocols/Middleware.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `Middleware` 4 | 5 | ```swift 6 | public protocol Middleware 7 | ``` 8 | 9 | ## Properties 10 | ### `id` 11 | 12 | ```swift 13 | var id: UUID 14 | ``` 15 | 16 | ### `perform` 17 | 18 | ```swift 19 | var perform: MiddlewareChain 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/Mini/protocols/OptionalType.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `OptionalType` 4 | 5 | ## Properties 6 | ### `value` 7 | -------------------------------------------------------------------------------- /docs/Mini/protocols/Service.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `Service` 4 | 5 | ```swift 6 | public protocol Service 7 | ``` 8 | 9 | ## Properties 10 | ### `id` 11 | 12 | ```swift 13 | var id: UUID 14 | ``` 15 | 16 | ### `perform` 17 | 18 | ```swift 19 | var perform: ServiceChain 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/Mini/protocols/StateType.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `StateType` 4 | 5 | ```swift 6 | public protocol StateType 7 | ``` 8 | 9 | ## Methods 10 | ### `isEqual(to:)` 11 | 12 | ```swift 13 | func isEqual(to other: StateType) -> Bool 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/Mini/protocols/StoreType.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `StoreType` 4 | 5 | ```swift 6 | public protocol StoreType 7 | ``` 8 | 9 | ## Properties 10 | ### `state` 11 | 12 | ```swift 13 | var state: State 14 | ``` 15 | 16 | ### `dispatcher` 17 | 18 | ```swift 19 | var dispatcher: Dispatcher 20 | ``` 21 | 22 | ### `reducerGroup` 23 | 24 | ```swift 25 | var reducerGroup: ReducerGroup 26 | ``` 27 | 28 | ## Methods 29 | ### `replayOnce()` 30 | 31 | ```swift 32 | func replayOnce() 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/Mini/structs/DispatchMode.md: -------------------------------------------------------------------------------- 1 | **STRUCT** 2 | 3 | # `DispatchMode` 4 | 5 | ```swift 6 | public struct DispatchMode 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/MiniPromises.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | import Foundation 3 | import Mini 4 | import RxSwift 5 | import SwiftOnoneSupport 6 | 7 | public protocol CompletableAction: Mini.Action, MiniPromises.PayloadAction {} 8 | 9 | public protocol EmptyAction: Mini.Action, MiniPromises.PayloadAction where Self.Payload == Void { 10 | init(promise: MiniPromises.Promise) 11 | } 12 | 13 | extension EmptyAction { 14 | public init(promise _: MiniPromises.Promise) 15 | } 16 | 17 | public protocol KeyedCompletableAction: Mini.Action, MiniPromises.KeyedPayloadAction {} 18 | 19 | public protocol KeyedPayloadAction { 20 | associatedtype Payload 21 | 22 | associatedtype Key: Hashable 23 | 24 | init(promise: [Self.Key: MiniPromises.Promise]) 25 | } 26 | 27 | public protocol PayloadAction { 28 | associatedtype Payload 29 | 30 | init(promise: MiniPromises.Promise) 31 | } 32 | 33 | @dynamicCallable @dynamicMemberLookup public final class Promise: MiniPromises.PromiseType { 34 | public typealias Element = T 35 | 36 | public class func value(_ value: T) -> MiniPromises.Promise 37 | 38 | public class func error(_ error: Error) -> MiniPromises.Promise 39 | 40 | public init(error: Error) 41 | 42 | public init() 43 | 44 | public class func idle(with options: [String: Any] = [:]) -> MiniPromises.Promise 45 | 46 | public class func pending(options: [String: Any] = [:]) -> MiniPromises.Promise 47 | 48 | public var result: Result? { get } 49 | 50 | /// - Note: `fulfill` do not trigger an object reassignment, 51 | /// so no notifications about it can be triggered. It is recommended 52 | /// to call the method `notify` afterwards. 53 | public func fulfill(_ value: T) -> Self 54 | 55 | /// - Note: `reject` do not trigger an object reassignment, 56 | /// so no notifications about it can be triggered. It is recommended 57 | /// to call the method `notify` afterwards. 58 | public func reject(_ error: Error) -> Self 59 | 60 | /// Resolves the current `Promise` with the optional `Result` parameter. 61 | /// - Returns: `self` or `nil` if no `result` was not provided. 62 | /// - Note: The optional parameter and restun value are helpers in order to 63 | /// make optional chaining in the `Reducer` context. 64 | public func resolve(_ result: Result?) -> Self? 65 | 66 | public subscript(dynamicMember _: String) -> Value? { get } 67 | 68 | public func dynamicallyCall(withKeywordArguments args: KeyValuePairs) 69 | } 70 | 71 | extension Promise { 72 | /** 73 | - Returns: `true` if the promise has not yet resolved nor pending. 74 | */ 75 | public var isIdle: Bool { get } 76 | 77 | /** 78 | - Returns: `true` if the promise has not yet resolved. 79 | */ 80 | public var isPending: Bool { get } 81 | 82 | /** 83 | - Returns: `true` if the promise has completed. 84 | */ 85 | public var isCompleted: Bool { get } 86 | 87 | /** 88 | - Returns: `true` if the promise has resolved. 89 | */ 90 | public var isResolved: Bool { get } 91 | 92 | /** 93 | - Returns: `true` if the promise was fulfilled. 94 | */ 95 | public var isFulfilled: Bool { get } 96 | 97 | /** 98 | - Returns: `true` if the promise was rejected. 99 | */ 100 | public var isRejected: Bool { get } 101 | 102 | /** 103 | - Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected. 104 | */ 105 | public var value: T? { get } 106 | 107 | /** 108 | - Returns: The error with which this promise was rejected or `nil` if this promise is pending or fulfilled. 109 | */ 110 | public var error: Error? { get } 111 | } 112 | 113 | extension Promise where T == () { 114 | public convenience init() 115 | 116 | public static func empty() -> MiniPromises.Promise 117 | } 118 | 119 | extension Promise: Equatable where T == () { 120 | /// Returns a Boolean value indicating whether two values are equal. 121 | /// 122 | /// Equality is the inverse of inequality. For any values `a` and `b`, 123 | /// `a == b` implies that `a != b` is `false`. 124 | /// 125 | /// - Parameters: 126 | /// - lhs: A value to compare. 127 | /// - rhs: Another value to compare. 128 | public static func == (lhs: MiniPromises.Promise, rhs: MiniPromises.Promise) -> Bool 129 | } 130 | 131 | extension Promise where T: Equatable { 132 | public static func == (lhs: MiniPromises.Promise, rhs: MiniPromises.Promise) -> Bool 133 | } 134 | 135 | extension Promise { 136 | public func notify(to store: T) where T: Mini.StoreType 137 | } 138 | 139 | public protocol PromiseType { 140 | associatedtype Element 141 | 142 | var result: Result? { get } 143 | 144 | var isIdle: Bool { get } 145 | 146 | var isPending: Bool { get } 147 | 148 | var isResolved: Bool { get } 149 | 150 | var isFulfilled: Bool { get } 151 | 152 | var isRejected: Bool { get } 153 | 154 | var value: Self.Element? { get } 155 | 156 | var error: Error? { get } 157 | 158 | func resolve(_ result: Result?) -> Self? 159 | 160 | func fulfill(_ value: Self.Element) -> Self 161 | 162 | func reject(_ error: Error) -> Self 163 | } 164 | 165 | public enum Promises {} 166 | 167 | extension Promises { 168 | public enum Lifetime { 169 | case once 170 | 171 | case forever(ignoringOld: Bool = false) 172 | } 173 | } 174 | 175 | extension Dictionary where Value: MiniPromises.PromiseType { 176 | public subscript(promise _: Key) -> Value { get } 177 | 178 | public func hasValue(for key: [Key: Value].Key) -> Bool 179 | 180 | public func resolve(with other: [Key: Value]) -> [Key: Value] 181 | 182 | public func mergingNew(with other: [Key: Value]) -> [Key: Value] 183 | } 184 | 185 | extension Dictionary where Value: MiniPromises.PromiseType, Value.Element: Equatable { 186 | public static func == (lhs: [Key: Value], rhs: [Key: Value]) -> Bool 187 | } 188 | 189 | extension ObservableType where Self.Element: Mini.StoreType, Self.Element: RxSwift.ObservableType, Self.Element.Element == Self.Element.State { 190 | @available(*, deprecated, message: "Use store.dispatch() or dispatcher.dispatch in conjunction with an Observable over the Store: withStateChanges, select") 191 | public static func dispatch(using dispatcher: Mini.Dispatcher? = nil, factory action: @autoclosure @escaping () -> A, taskMap: @escaping (Self.Element.State) -> T?, on store: Self.Element, lifetime: MiniPromises.Promises.Lifetime = .once) -> RxSwift.Observable where A: Mini.Action, T: MiniPromises.Promise 192 | 193 | @available(*, deprecated, message: "Use store.dispatch() or dispatcher.dispatch in conjunction with an Observable over the Store: withStateChanges, select") 194 | public static func dispatch(using dispatcher: Mini.Dispatcher? = nil, factory action: @autoclosure @escaping () -> A, key: K, taskMap: @escaping (Self.Element.State) -> [K: T], on store: Self.Element, lifetime: MiniPromises.Promises.Lifetime = .once) -> RxSwift.Observable where A: Mini.Action, K: Hashable, T: MiniPromises.Promise 195 | } 196 | 197 | extension PrimitiveSequenceType where Self: RxSwift.ObservableConvertibleType, Self.Trait == RxSwift.SingleTrait { 198 | public func dispatch(action: A.Type, on dispatcher: Mini.Dispatcher, mode: Mini.Dispatcher.DispatchMode.UI = .async, fillOnError errorPayload: A.Payload? = nil) -> RxSwift.Disposable where A: MiniPromises.CompletableAction, Self.Element == A.Payload 199 | 200 | public func dispatch(action: A.Type, key: A.Key, on dispatcher: Mini.Dispatcher, mode: Mini.Dispatcher.DispatchMode.UI = .async, fillOnError errorPayload: A.Payload? = nil) -> RxSwift.Disposable where A: MiniPromises.KeyedCompletableAction, Self.Element == A.Payload 201 | 202 | public func action(_ action: A.Type, fillOnError errorPayload: A.Payload? = nil) -> RxSwift.Single where A: MiniPromises.CompletableAction, Self.Element == A.Payload 203 | } 204 | 205 | extension PrimitiveSequenceType where Self.Element == Never, Self.Trait == RxSwift.CompletableTrait { 206 | public func dispatch(action: A.Type, on dispatcher: Mini.Dispatcher, mode: Mini.Dispatcher.DispatchMode.UI = .async) -> RxSwift.Disposable where A: MiniPromises.EmptyAction 207 | 208 | public func action(_ action: A.Type) -> RxSwift.Single where A: MiniPromises.EmptyAction 209 | } 210 | -------------------------------------------------------------------------------- /docs/MiniPromises/README.md: -------------------------------------------------------------------------------- 1 | ## Protocols 2 | 3 | - [CompletableAction](protocols/CompletableAction.md) 4 | - [EmptyAction](protocols/EmptyAction.md) 5 | - [KeyedCompletableAction](protocols/KeyedCompletableAction.md) 6 | - [KeyedPayloadAction](protocols/KeyedPayloadAction.md) 7 | - [PayloadAction](protocols/PayloadAction.md) 8 | - [PromiseType](protocols/PromiseType.md) 9 | 10 | ## Classes 11 | 12 | - [Promise](classes/Promise.md) 13 | 14 | ## Enums 15 | 16 | - [Lifetime](enums/Lifetime.md) 17 | - [Promises](enums/Promises.md) 18 | 19 | ## Extensions 20 | 21 | - [Dictionary](extensions/Dictionary.md) 22 | - [EmptyAction](extensions/EmptyAction.md) 23 | - [ObservableType](extensions/ObservableType.md) 24 | - [PrimitiveSequenceType](extensions/PrimitiveSequenceType.md) 25 | - [Promise](extensions/Promise.md) 26 | 27 | # Reference Documentation 28 | This reference documentation was generated with 29 | [SourceDocs](https://github.com/eneko/SourceDocs). -------------------------------------------------------------------------------- /docs/MiniPromises/classes/Promise.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `Promise` 4 | 5 | ```swift 6 | public final class Promise: PromiseType 7 | ``` 8 | 9 | ## Properties 10 | ### `result` 11 | 12 | ```swift 13 | public var result: Result? 14 | ``` 15 | 16 | ## Methods 17 | ### `value(_:)` 18 | 19 | ```swift 20 | public class func value(_ value: T) -> Promise 21 | ``` 22 | 23 | ### `error(_:)` 24 | 25 | ```swift 26 | public class func error(_ error: Swift.Error) -> Promise 27 | ``` 28 | 29 | ### `init(error:)` 30 | 31 | ```swift 32 | public init(error: Swift.Error) 33 | ``` 34 | 35 | ### `init()` 36 | 37 | ```swift 38 | public init() 39 | ``` 40 | 41 | ### `idle(with:)` 42 | 43 | ```swift 44 | public class func idle(with options: [String: Any] = [:]) -> Promise 45 | ``` 46 | 47 | ### `pending(options:)` 48 | 49 | ```swift 50 | public class func pending(options: [String: Any] = [:]) -> Promise 51 | ``` 52 | 53 | ### `fulfill(_:)` 54 | 55 | ```swift 56 | public func fulfill(_ value: T) -> Self 57 | ``` 58 | 59 | > - Note: `fulfill` do not trigger an object reassignment, 60 | > so no notifications about it can be triggered. It is recommended 61 | > to call the method `notify` afterwards. 62 | 63 | ### `reject(_:)` 64 | 65 | ```swift 66 | public func reject(_ error: Swift.Error) -> Self 67 | ``` 68 | 69 | > - Note: `reject` do not trigger an object reassignment, 70 | > so no notifications about it can be triggered. It is recommended 71 | > to call the method `notify` afterwards. 72 | 73 | ### `resolve(_:)` 74 | 75 | ```swift 76 | public func resolve(_ result: Result?) -> Self? 77 | ``` 78 | 79 | > Resolves the current `Promise` with the optional `Result` parameter. 80 | > - Returns: `self` or `nil` if no `result` was not provided. 81 | > - Note: The optional parameter and restun value are helpers in order to 82 | > make optional chaining in the `Reducer` context. 83 | 84 | ### `dynamicallyCall(withKeywordArguments:)` 85 | 86 | ```swift 87 | public func dynamicallyCall(withKeywordArguments args: KeyValuePairs) 88 | ``` 89 | -------------------------------------------------------------------------------- /docs/MiniPromises/enums/Lifetime.md: -------------------------------------------------------------------------------- 1 | **ENUM** 2 | 3 | # `Lifetime` 4 | 5 | ```swift 6 | enum Lifetime 7 | ``` 8 | 9 | ## Cases 10 | ### `once` 11 | 12 | ```swift 13 | case once 14 | ``` 15 | 16 | ### `forever(ignoringOld:)` 17 | 18 | ```swift 19 | case forever(ignoringOld: Bool = false) 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/MiniPromises/enums/Promises.md: -------------------------------------------------------------------------------- 1 | **ENUM** 2 | 3 | # `Promises` 4 | 5 | ```swift 6 | public enum Promises 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/MiniPromises/extensions/Dictionary.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `Dictionary` 4 | 5 | ## Methods 6 | ### `hasValue(for:)` 7 | 8 | ```swift 9 | public func hasValue(for key: Dictionary.Key) -> Bool 10 | ``` 11 | 12 | ### `resolve(with:)` 13 | 14 | ```swift 15 | public func resolve(with other: [Key: Value]) -> Self 16 | ``` 17 | 18 | ### `mergingNew(with:)` 19 | 20 | ```swift 21 | public func mergingNew(with other: [Key: Value]) -> Self 22 | ``` 23 | 24 | ### `==(_:_:)` 25 | 26 | ```swift 27 | static func == (lhs: [Key: Value], rhs: [Key: Value]) -> Bool 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/MiniPromises/extensions/EmptyAction.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `EmptyAction` 4 | 5 | ## Methods 6 | ### `init(promise:)` 7 | 8 | ```swift 9 | init(promise _: Promise) 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/MiniPromises/extensions/ObservableType.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `ObservableType` 4 | 5 | ## Methods 6 | ### `dispatch(using:factory:taskMap:on:lifetime:)` 7 | 8 | ```swift 9 | public static func dispatch>( 10 | using dispatcher: Dispatcher? = nil, 11 | factory action: @autoclosure @escaping () -> A, 12 | taskMap: @escaping (Self.Element.State) -> T?, 13 | on store: Self.Element, 14 | lifetime: Promises.Lifetime = .once 15 | ) 16 | -> Observable 17 | ``` 18 | 19 | ### `dispatch(using:factory:key:taskMap:on:lifetime:)` 20 | 21 | ```swift 22 | public static func dispatch>( 23 | using dispatcher: Dispatcher? = nil, 24 | factory action: @autoclosure @escaping () -> A, 25 | key: K, 26 | taskMap: @escaping (Self.Element.State) -> [K: T], 27 | on store: Self.Element, 28 | lifetime: Promises.Lifetime = .once 29 | ) 30 | -> Observable 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/MiniPromises/extensions/PrimitiveSequenceType.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `PrimitiveSequenceType` 4 | 5 | ## Methods 6 | ### `dispatch(action:on:mode:fillOnError:)` 7 | 8 | ```swift 9 | func dispatch(action: A.Type, 10 | on dispatcher: Dispatcher, 11 | mode: Dispatcher.DispatchMode.UI = .async, 12 | fillOnError errorPayload: A.Payload? = nil) 13 | -> Disposable where A.Payload == Self.Element 14 | ``` 15 | 16 | ### `dispatch(action:key:on:mode:fillOnError:)` 17 | 18 | ```swift 19 | func dispatch(action: A.Type, 20 | key: A.Key, 21 | on dispatcher: Dispatcher, 22 | mode: Dispatcher.DispatchMode.UI = .async, 23 | fillOnError errorPayload: A.Payload? = nil) 24 | -> Disposable where A.Payload == Self.Element 25 | ``` 26 | 27 | ### `action(_:fillOnError:)` 28 | 29 | ```swift 30 | func action(_ action: A.Type, 31 | fillOnError errorPayload: A.Payload? = nil) 32 | -> Single where A.Payload == Self.Element 33 | ``` 34 | 35 | ### `dispatch(action:on:mode:)` 36 | 37 | ```swift 38 | func dispatch(action: A.Type, 39 | on dispatcher: Dispatcher, 40 | mode: Dispatcher.DispatchMode.UI = .async) 41 | -> Disposable 42 | ``` 43 | 44 | ### `action(_:)` 45 | 46 | ```swift 47 | func action(_ action: A.Type) 48 | -> Single 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/MiniPromises/extensions/Promise.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `Promise` 4 | 5 | ## Properties 6 | ### `isIdle` 7 | 8 | ```swift 9 | var isIdle: Bool 10 | ``` 11 | 12 | > - Returns: `true` if the promise has not yet resolved nor pending. 13 | 14 | ### `isPending` 15 | 16 | ```swift 17 | var isPending: Bool 18 | ``` 19 | 20 | > - Returns: `true` if the promise has not yet resolved. 21 | 22 | ### `isCompleted` 23 | 24 | ```swift 25 | var isCompleted: Bool 26 | ``` 27 | 28 | > - Returns: `true` if the promise has completed. 29 | 30 | ### `isResolved` 31 | 32 | ```swift 33 | var isResolved: Bool 34 | ``` 35 | 36 | > - Returns: `true` if the promise has resolved. 37 | 38 | ### `isFulfilled` 39 | 40 | ```swift 41 | var isFulfilled: Bool 42 | ``` 43 | 44 | > - Returns: `true` if the promise was fulfilled. 45 | 46 | ### `isRejected` 47 | 48 | ```swift 49 | var isRejected: Bool 50 | ``` 51 | 52 | > - Returns: `true` if the promise was rejected. 53 | 54 | ### `value` 55 | 56 | ```swift 57 | var value: T? 58 | ``` 59 | 60 | > - Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected. 61 | 62 | ### `error` 63 | 64 | ```swift 65 | var error: Swift.Error? 66 | ``` 67 | 68 | > - Returns: The error with which this promise was rejected or `nil` if this promise is pending or fulfilled. 69 | 70 | ## Methods 71 | ### `init()` 72 | 73 | ```swift 74 | public convenience init() 75 | ``` 76 | 77 | ### `empty()` 78 | 79 | ```swift 80 | public static func empty() -> Promise 81 | ``` 82 | 83 | ### `==(_:_:)` 84 | 85 | ```swift 86 | public static func == (lhs: Promise, rhs: Promise) -> Bool 87 | ``` 88 | 89 | #### Parameters 90 | 91 | | Name | Description | 92 | | ---- | ----------- | 93 | | lhs | A value to compare. | 94 | | rhs | Another value to compare. | 95 | 96 | ### `==(_:_:)` 97 | 98 | ```swift 99 | public static func == (lhs: Promise, rhs: Promise) -> Bool 100 | ``` 101 | 102 | ### `notify(to:)` 103 | 104 | ```swift 105 | func notify(to store: T) 106 | ``` 107 | -------------------------------------------------------------------------------- /docs/MiniPromises/protocols/CompletableAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `CompletableAction` 4 | 5 | ```swift 6 | public protocol CompletableAction: Action & PayloadAction 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/MiniPromises/protocols/EmptyAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `EmptyAction` 4 | 5 | ```swift 6 | public protocol EmptyAction: Action & PayloadAction where Payload == Swift.Void 7 | ``` 8 | 9 | ## Methods 10 | ### `init(promise:)` 11 | 12 | ```swift 13 | init(promise: Promise) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/MiniPromises/protocols/KeyedCompletableAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `KeyedCompletableAction` 4 | 5 | ```swift 6 | public protocol KeyedCompletableAction: Action & KeyedPayloadAction 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/MiniPromises/protocols/KeyedPayloadAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `KeyedPayloadAction` 4 | 5 | ```swift 6 | public protocol KeyedPayloadAction 7 | ``` 8 | 9 | ## Methods 10 | ### `init(promise:)` 11 | 12 | ```swift 13 | init(promise: [Key: Promise]) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/MiniPromises/protocols/PayloadAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `PayloadAction` 4 | 5 | ```swift 6 | public protocol PayloadAction 7 | ``` 8 | 9 | ## Methods 10 | ### `init(promise:)` 11 | 12 | ```swift 13 | init(promise: Promise) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/MiniPromises/protocols/PromiseType.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `PromiseType` 4 | 5 | ```swift 6 | public protocol PromiseType 7 | ``` 8 | 9 | ## Properties 10 | ### `result` 11 | 12 | ```swift 13 | var result: Result? 14 | ``` 15 | 16 | ### `isIdle` 17 | 18 | ```swift 19 | var isIdle: Bool 20 | ``` 21 | 22 | ### `isPending` 23 | 24 | ```swift 25 | var isPending: Bool 26 | ``` 27 | 28 | ### `isResolved` 29 | 30 | ```swift 31 | var isResolved: Bool 32 | ``` 33 | 34 | ### `isFulfilled` 35 | 36 | ```swift 37 | var isFulfilled: Bool 38 | ``` 39 | 40 | ### `isRejected` 41 | 42 | ```swift 43 | var isRejected: Bool 44 | ``` 45 | 46 | ### `value` 47 | 48 | ```swift 49 | var value: Element? 50 | ``` 51 | 52 | ### `error` 53 | 54 | ```swift 55 | var error: Swift.Error? 56 | ``` 57 | 58 | ## Methods 59 | ### `resolve(_:)` 60 | 61 | ```swift 62 | func resolve(_ result: Result?) -> Self? 63 | ``` 64 | 65 | ### `fulfill(_:)` 66 | 67 | ```swift 68 | func fulfill(_ value: Element) -> Self 69 | ``` 70 | 71 | ### `reject(_:)` 72 | 73 | ```swift 74 | func reject(_ error: Swift.Error) -> Self 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/MiniTasks.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Mini 3 | import RxSwift 4 | import SwiftOnoneSupport 5 | 6 | public typealias AnyTask = MiniTasks.TypedTask 7 | 8 | public protocol CompletableAction: Mini.Action, MiniTasks.PayloadAction {} 9 | 10 | public protocol EmptyAction: Mini.Action, MiniTasks.PayloadAction where Self.Payload == Never { 11 | init(task: MiniTasks.AnyTask) 12 | } 13 | 14 | extension EmptyAction { 15 | public init(task _: MiniTasks.AnyTask, payload _: Self.Payload?) 16 | } 17 | 18 | public protocol KeyedCompletableAction: Mini.Action, MiniTasks.KeyedPayloadAction {} 19 | 20 | public protocol KeyedPayloadAction { 21 | associatedtype Payload 22 | 23 | associatedtype Key: Hashable 24 | 25 | init(task: MiniTasks.AnyTask, payload: Self.Payload?, key: Self.Key) 26 | } 27 | 28 | public typealias KeyedTask = [K: MiniTasks.AnyTask] where K: Hashable 29 | 30 | public protocol PayloadAction { 31 | associatedtype Payload 32 | 33 | init(task: MiniTasks.AnyTask, payload: Self.Payload?) 34 | } 35 | 36 | @dynamicMemberLookup public class TypedTask: Equatable { 37 | public enum Status { 38 | case idle 39 | 40 | case running 41 | 42 | case success 43 | 44 | case error 45 | } 46 | 47 | public let status: MiniTasks.TypedTask.Status 48 | 49 | public let data: T? 50 | 51 | public let error: Error? 52 | 53 | public let initDate: Date 54 | 55 | public init(status: MiniTasks.TypedTask.Status = .idle, data: T? = nil, error: Error? = nil) 56 | 57 | public static func idle() -> MiniTasks.AnyTask 58 | 59 | public static func running() -> MiniTasks.AnyTask 60 | 61 | public static func success(_ data: T? = nil) -> MiniTasks.TypedTask 62 | 63 | public static func success() -> MiniTasks.AnyTask 64 | 65 | public static func failure(_ error: Error) -> MiniTasks.AnyTask 66 | 67 | public var isRunning: Bool { get } 68 | 69 | public var isCompleted: Bool { get } 70 | 71 | public var isSuccessful: Bool { get } 72 | 73 | public var isFailure: Bool { get } 74 | 75 | public subscript(dynamicMember member: String) -> Value? 76 | 77 | /// Returns a Boolean value indicating whether two values are equal. 78 | /// 79 | /// Equality is the inverse of inequality. For any values `a` and `b`, 80 | /// `a == b` implies that `a != b` is `false`. 81 | /// 82 | /// - Parameters: 83 | /// - lhs: A value to compare. 84 | /// - rhs: Another value to compare. 85 | public static func == (lhs: MiniTasks.TypedTask, rhs: MiniTasks.TypedTask) -> Bool 86 | } 87 | 88 | extension ObservableType where Self.Element: Mini.StateType { 89 | /** 90 | Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes. 91 | */ 92 | public func withStateChanges(in stateComponent: KeyPath) -> RxSwift.Observable 93 | 94 | /** 95 | Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes using a `taskComponent` (i.e. a Task component in the State) to be completed (either successfully or failed). 96 | */ 97 | public func withStateChanges(in stateComponent: KeyPath, that taskComponent: KeyPath) -> RxSwift.Observable where U: MiniTasks.TypedTask 98 | } 99 | 100 | extension PrimitiveSequenceType where Self: RxSwift.ObservableConvertibleType, Self.Trait == RxSwift.SingleTrait { 101 | /** 102 | Dispatches an given action from the result of the `Single` trait. This is only usable when the `Action` is a `CompletableAction`. 103 | - Parameter action: The `CompletableAction` type to be dispatched. 104 | - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 105 | - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 106 | - Parameter fillOnError: The payload that will replace the action's payload in case of failure. 107 | */ 108 | public func dispatch(action: A.Type, on dispatcher: Mini.Dispatcher, mode: Mini.Dispatcher.DispatchMode.UI = .async, fillOnError errorPayload: A.Payload? = nil) -> RxSwift.Disposable where A: MiniTasks.CompletableAction, Self.Element == A.Payload 109 | 110 | /** 111 | Dispatches an given action from the result of the `Single` trait. This is only usable when the `Action` is a `CompletableAction`. 112 | - Parameter action: The `CompletableAction` type to be dispatched. 113 | - Parameter key: The key associated with the `Task` result. 114 | - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 115 | - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 116 | - Parameter fillOnError: The payload that will replace the action's payload in case of failure or `nil`. 117 | */ 118 | public func dispatch(action: A.Type, key: A.Key, on dispatcher: Mini.Dispatcher, mode: Mini.Dispatcher.DispatchMode.UI = .async, fillOnError errorPayload: A.Payload? = nil) -> RxSwift.Disposable where A: MiniTasks.KeyedCompletableAction, Self.Element == A.Payload 119 | 120 | /** 121 | Builds a `CompletableAction` from a `Single` 122 | - Parameter action: The `CompletableAction` type to be built. 123 | - Parameter fillOnError: The payload that will replace the action's payload in case of failure or `nil`. 124 | - Returns: A `Single` of the `CompletableAction` type declared by the action parameter. 125 | */ 126 | public func action(_ action: A.Type, fillOnError errorPayload: A.Payload? = nil) -> RxSwift.Single where A: MiniTasks.CompletableAction, Self.Element == A.Payload 127 | } 128 | 129 | extension PrimitiveSequenceType where Self.Element == Never, Self.Trait == RxSwift.CompletableTrait { 130 | /** 131 | Dispatches an given action from the result of the `Completable` trait. This is only usable when the `Action` is an `EmptyAction`. 132 | - Parameter action: The `CompletableAction` type to be dispatched. 133 | - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 134 | - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 135 | */ 136 | public func dispatch(action: A.Type, on dispatcher: Mini.Dispatcher, mode: Mini.Dispatcher.DispatchMode.UI = .async) -> RxSwift.Disposable where A: MiniTasks.EmptyAction 137 | 138 | /** 139 | Builds an `EmptyAction` from a `Completable` 140 | - Parameter action: The `EmptyAction` type to be built. 141 | - Returns: A `Single` of the `EmptyAction` type declared by the action parameter. 142 | */ 143 | public func action(_ action: A.Type) -> RxSwift.Single where A: MiniTasks.EmptyAction 144 | } 145 | -------------------------------------------------------------------------------- /docs/MiniTasks/README.md: -------------------------------------------------------------------------------- 1 | ## Protocols 2 | 3 | - [CompletableAction](protocols/CompletableAction.md) 4 | - [EmptyAction](protocols/EmptyAction.md) 5 | - [KeyedCompletableAction](protocols/KeyedCompletableAction.md) 6 | - [KeyedPayloadAction](protocols/KeyedPayloadAction.md) 7 | - [PayloadAction](protocols/PayloadAction.md) 8 | 9 | ## Classes 10 | 11 | - [TypedTask](classes/TypedTask.md) 12 | 13 | ## Enums 14 | 15 | - [Status](enums/Status.md) 16 | 17 | ## Extensions 18 | 19 | - [EmptyAction](extensions/EmptyAction.md) 20 | - [ObservableType](extensions/ObservableType.md) 21 | - [PrimitiveSequenceType](extensions/PrimitiveSequenceType.md) 22 | 23 | # Reference Documentation 24 | This reference documentation was generated with 25 | [SourceDocs](https://github.com/eneko/SourceDocs). -------------------------------------------------------------------------------- /docs/MiniTasks/classes/TypedTask.md: -------------------------------------------------------------------------------- 1 | **CLASS** 2 | 3 | # `TypedTask` 4 | 5 | ```swift 6 | public class TypedTask: Equatable 7 | ``` 8 | 9 | ## Properties 10 | ### `status` 11 | 12 | ```swift 13 | public let status: Status 14 | ``` 15 | 16 | ### `data` 17 | 18 | ```swift 19 | public let data: T? 20 | ``` 21 | 22 | ### `error` 23 | 24 | ```swift 25 | public let error: Error? 26 | ``` 27 | 28 | ### `initDate` 29 | 30 | ```swift 31 | public let initDate = Date() 32 | ``` 33 | 34 | ### `isRunning` 35 | 36 | ```swift 37 | public var isRunning: Bool 38 | ``` 39 | 40 | ### `isCompleted` 41 | 42 | ```swift 43 | public var isCompleted: Bool 44 | ``` 45 | 46 | ### `isSuccessful` 47 | 48 | ```swift 49 | public var isSuccessful: Bool 50 | ``` 51 | 52 | ### `isFailure` 53 | 54 | ```swift 55 | public var isFailure: Bool 56 | ``` 57 | 58 | ## Methods 59 | ### `init(status:data:error:)` 60 | 61 | ```swift 62 | public init(status: Status = .idle, 63 | data: T? = nil, 64 | error: Error? = nil) 65 | ``` 66 | 67 | ### `idle()` 68 | 69 | ```swift 70 | public static func idle() -> AnyTask 71 | ``` 72 | 73 | ### `running()` 74 | 75 | ```swift 76 | public static func running() -> AnyTask 77 | ``` 78 | 79 | ### `success(_:)` 80 | 81 | ```swift 82 | public static func success(_ data: T? = nil) -> TypedTask 83 | ``` 84 | 85 | ### `success()` 86 | 87 | ```swift 88 | public static func success() -> AnyTask 89 | ``` 90 | 91 | ### `failure(_:)` 92 | 93 | ```swift 94 | public static func failure(_ error: Error) -> AnyTask 95 | ``` 96 | 97 | ### `==(_:_:)` 98 | 99 | ```swift 100 | public static func == (lhs: TypedTask, rhs: TypedTask) -> Bool 101 | ``` 102 | 103 | #### Parameters 104 | 105 | | Name | Description | 106 | | ---- | ----------- | 107 | | lhs | A value to compare. | 108 | | rhs | Another value to compare. | -------------------------------------------------------------------------------- /docs/MiniTasks/enums/Status.md: -------------------------------------------------------------------------------- 1 | **ENUM** 2 | 3 | # `Status` 4 | 5 | ```swift 6 | public enum Status 7 | ``` 8 | 9 | ## Cases 10 | ### `idle` 11 | 12 | ```swift 13 | case idle 14 | ``` 15 | 16 | ### `running` 17 | 18 | ```swift 19 | case running 20 | ``` 21 | 22 | ### `success` 23 | 24 | ```swift 25 | case success 26 | ``` 27 | 28 | ### `error` 29 | 30 | ```swift 31 | case error 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/MiniTasks/extensions/EmptyAction.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `EmptyAction` 4 | 5 | ## Methods 6 | ### `init(task:payload:)` 7 | 8 | ```swift 9 | init(task _: AnyTask, payload _: Payload?) 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/MiniTasks/extensions/ObservableType.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `ObservableType` 4 | 5 | ## Methods 6 | ### `withStateChanges(in:)` 7 | 8 | ```swift 9 | func withStateChanges( 10 | in stateComponent: KeyPath 11 | ) -> Observable 12 | ``` 13 | 14 | > Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes. 15 | 16 | ### `withStateChanges(in:that:)` 17 | 18 | ```swift 19 | func withStateChanges>( 20 | in stateComponent: KeyPath, 21 | that taskComponent: KeyPath 22 | ) 23 | -> Observable 24 | ``` 25 | 26 | > Maps from a `StateType` property to create an `Observable` that contains the filtered property and all its changes using a `taskComponent` (i.e. a Task component in the State) to be completed (either successfully or failed). 27 | -------------------------------------------------------------------------------- /docs/MiniTasks/extensions/PrimitiveSequenceType.md: -------------------------------------------------------------------------------- 1 | **EXTENSION** 2 | 3 | # `PrimitiveSequenceType` 4 | 5 | ## Methods 6 | ### `dispatch(action:on:mode:fillOnError:)` 7 | 8 | ```swift 9 | func dispatch(action: A.Type, 10 | on dispatcher: Dispatcher, 11 | mode: Dispatcher.DispatchMode.UI = .async, 12 | fillOnError errorPayload: A.Payload? = nil) 13 | -> Disposable where A.Payload == Self.Element 14 | ``` 15 | 16 | > Dispatches an given action from the result of the `Single` trait. This is only usable when the `Action` is a `CompletableAction`. 17 | > - Parameter action: The `CompletableAction` type to be dispatched. 18 | > - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 19 | > - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 20 | > - Parameter fillOnError: The payload that will replace the action's payload in case of failure. 21 | 22 | #### Parameters 23 | 24 | | Name | Description | 25 | | ---- | ----------- | 26 | | action | The `CompletableAction` type to be dispatched. | 27 | | dispatcher | The `Dispatcher` object that will dispatch the action. | 28 | | mode | The `Dispatcher` dispatch mode, `.async` by default. | 29 | | fillOnError | The payload that will replace the action’s payload in case of failure. | 30 | 31 | ### `dispatch(action:key:on:mode:fillOnError:)` 32 | 33 | ```swift 34 | func dispatch(action: A.Type, 35 | key: A.Key, 36 | on dispatcher: Dispatcher, 37 | mode: Dispatcher.DispatchMode.UI = .async, 38 | fillOnError errorPayload: A.Payload? = nil) 39 | -> Disposable where A.Payload == Self.Element 40 | ``` 41 | 42 | > Dispatches an given action from the result of the `Single` trait. This is only usable when the `Action` is a `CompletableAction`. 43 | > - Parameter action: The `CompletableAction` type to be dispatched. 44 | > - Parameter key: The key associated with the `Task` result. 45 | > - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 46 | > - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 47 | > - Parameter fillOnError: The payload that will replace the action's payload in case of failure or `nil`. 48 | 49 | #### Parameters 50 | 51 | | Name | Description | 52 | | ---- | ----------- | 53 | | action | The `CompletableAction` type to be dispatched. | 54 | | key | The key associated with the `Task` result. | 55 | | dispatcher | The `Dispatcher` object that will dispatch the action. | 56 | | mode | The `Dispatcher` dispatch mode, `.async` by default. | 57 | | fillOnError | The payload that will replace the action’s payload in case of failure or `nil`. | 58 | 59 | ### `action(_:fillOnError:)` 60 | 61 | ```swift 62 | func action(_ action: A.Type, 63 | fillOnError errorPayload: A.Payload? = nil) 64 | -> Single where A.Payload == Self.Element 65 | ``` 66 | 67 | > Builds a `CompletableAction` from a `Single` 68 | > - Parameter action: The `CompletableAction` type to be built. 69 | > - Parameter fillOnError: The payload that will replace the action's payload in case of failure or `nil`. 70 | > - Returns: A `Single` of the `CompletableAction` type declared by the action parameter. 71 | 72 | #### Parameters 73 | 74 | | Name | Description | 75 | | ---- | ----------- | 76 | | action | The `CompletableAction` type to be built. | 77 | | fillOnError | The payload that will replace the action’s payload in case of failure or `nil`. | 78 | 79 | ### `dispatch(action:on:mode:)` 80 | 81 | ```swift 82 | func dispatch(action: A.Type, 83 | on dispatcher: Dispatcher, 84 | mode: Dispatcher.DispatchMode.UI = .async) 85 | -> Disposable 86 | ``` 87 | 88 | > Dispatches an given action from the result of the `Completable` trait. This is only usable when the `Action` is an `EmptyAction`. 89 | > - Parameter action: The `CompletableAction` type to be dispatched. 90 | > - Parameter dispatcher: The `Dispatcher` object that will dispatch the action. 91 | > - Parameter mode: The `Dispatcher` dispatch mode, `.async` by default. 92 | 93 | #### Parameters 94 | 95 | | Name | Description | 96 | | ---- | ----------- | 97 | | action | The `CompletableAction` type to be dispatched. | 98 | | dispatcher | The `Dispatcher` object that will dispatch the action. | 99 | | mode | The `Dispatcher` dispatch mode, `.async` by default. | 100 | 101 | ### `action(_:)` 102 | 103 | ```swift 104 | func action(_ action: A.Type) 105 | -> Single 106 | ``` 107 | 108 | > Builds an `EmptyAction` from a `Completable` 109 | > - Parameter action: The `EmptyAction` type to be built. 110 | > - Returns: A `Single` of the `EmptyAction` type declared by the action parameter. 111 | 112 | #### Parameters 113 | 114 | | Name | Description | 115 | | ---- | ----------- | 116 | | action | The `EmptyAction` type to be built. | -------------------------------------------------------------------------------- /docs/MiniTasks/protocols/CompletableAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `CompletableAction` 4 | 5 | ```swift 6 | public protocol CompletableAction: Action & PayloadAction 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/MiniTasks/protocols/EmptyAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `EmptyAction` 4 | 5 | ```swift 6 | public protocol EmptyAction: Action & PayloadAction where Payload == Swift.Never 7 | ``` 8 | 9 | ## Methods 10 | ### `init(task:)` 11 | 12 | ```swift 13 | init(task: AnyTask) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/MiniTasks/protocols/KeyedCompletableAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `KeyedCompletableAction` 4 | 5 | ```swift 6 | public protocol KeyedCompletableAction: Action & KeyedPayloadAction 7 | ``` 8 | -------------------------------------------------------------------------------- /docs/MiniTasks/protocols/KeyedPayloadAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `KeyedPayloadAction` 4 | 5 | ```swift 6 | public protocol KeyedPayloadAction 7 | ``` 8 | 9 | ## Methods 10 | ### `init(task:payload:key:)` 11 | 12 | ```swift 13 | init(task: AnyTask, payload: Payload?, key: Key) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/MiniTasks/protocols/PayloadAction.md: -------------------------------------------------------------------------------- 1 | **PROTOCOL** 2 | 3 | # `PayloadAction` 4 | 5 | ```swift 6 | public protocol PayloadAction 7 | ``` 8 | 9 | ## Methods 10 | ### `init(task:payload:)` 11 | 12 | ```swift 13 | init(task: AnyTask, payload: Payload?) 14 | ``` 15 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Documentation for Mini 2 | 3 | ## [Mini](Mini/README.md) 4 | 5 | ## [MiniTasks](MiniTasks/README.md) 6 | 7 | ## [MiniPromises](MiniPromises/README.md) -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | default_platform(:ios) 2 | 3 | platform :ios do 4 | desc "Pass all test for main target" 5 | lane :pass_tests do 6 | ENV["XCPRETTY_INHIBIT_WARNINGS"] = "1" 7 | sh 'swift package generate-xcodeproj' 8 | tests(platform: 'osx') 9 | tests(platform: 'ios') 10 | tests(platform: 'tvos') 11 | end 12 | 13 | lane :tests do |options| 14 | platform = options[:platform] 15 | print("PLATFORM #{platform}\n") 16 | map_platform_to_destination = { 17 | ios: 'platform=iOS Simulator,name=iPhone 8,OS=13.0', 18 | tvos: 'platform=tvOS Simulator,name=Apple TV,OS=13.0', 19 | osx: 'platform=OS X,arch=x86_64' 20 | } 21 | clear_derived_data 22 | Dir.chdir('..') do 23 | sh "xcodebuild -scheme Mini-Package -enableCodeCoverage YES clean test -destination '#{map_platform_to_destination[platform.to_sym]}' | xcpretty -f `xcpretty-travis-formatter`" 24 | sh "curl -s https://codecov.io/bash | bash -s -- -F #{platform} -J 'Mini'" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-trainer' 6 | gem "fastlane-plugin-test_scheme", git: "https://github.com/bq/fastlane-plugin-test_scheme" -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | 5 | Make sure you have the latest version of the Xcode command line tools installed: 6 | 7 | ``` 8 | xcode-select --install 9 | ``` 10 | 11 | Install _fastlane_ using 12 | ``` 13 | [sudo] gem install fastlane -NV 14 | ``` 15 | or alternatively using `brew cask install fastlane` 16 | 17 | # Available Actions 18 | ## iOS 19 | ### ios pass_tests 20 | ``` 21 | fastlane ios pass_tests 22 | ``` 23 | Pass all test for main target 24 | ### ios tests 25 | ``` 26 | fastlane ios tests 27 | ``` 28 | 29 | 30 | ---- 31 | 32 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 33 | More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). 34 | The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 35 | -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LastAccessedDate 6 | 2019-11-14T12:28:26Z 7 | WorkspacePath 8 | /Users/minuscorp/Documents/GitHub/mini-swift/Mini.xcodeproj 9 | 10 | 11 | --------------------------------------------------------------------------------