├── .codecov.yml
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── feature_request.md
│ └── proposal.md
├── dependabot.yml
├── images
│ ├── hero-light.svg
│ ├── kotlin-foundation.png
│ └── mobile-native-foundation.png
└── workflows
│ ├── add_issue_to_project.yml
│ ├── ci.yml
│ └── create_swift_package.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Images
├── friendly_robot.png
├── friendly_robot_icon.png
├── store-1.jpg
├── store-2.jpg
├── store-3.jpg
├── store-4.jpg
└── store-5.jpg
├── LICENSE
├── README.md
├── RELEASING.md
├── build.gradle.kts
├── cache
├── README.md
├── api
│ ├── android
│ │ └── cache.api
│ └── jvm
│ │ └── cache.api
├── build.gradle.kts
├── config
│ └── ktlint
│ │ └── baseline.xml
├── gradle.properties
└── src
│ ├── androidMain
│ └── AndroidManifest.xml
│ ├── commonMain
│ └── kotlin
│ │ └── org
│ │ └── mobilenativefoundation
│ │ └── store
│ │ └── cache5
│ │ ├── Cache.kt
│ │ ├── CacheBuilder.kt
│ │ ├── LocalCache.kt
│ │ ├── MonotonicTicker.kt
│ │ ├── RemovalCause.kt
│ │ ├── StoreMultiCache.kt
│ │ ├── StoreMultiCacheAccessor.kt
│ │ ├── Ticker.kt
│ │ └── Weigher.kt
│ └── commonTest
│ └── kotlin
│ └── org
│ └── mobilenativefoundation
│ └── store
│ └── cache5
│ └── CacheTests.kt
├── config
└── ktlint
│ └── baseline.xml
├── core
├── api
│ ├── android
│ │ └── core.api
│ └── jvm
│ │ └── core.api
├── build.gradle.kts
├── config
│ └── ktlint
│ │ └── baseline.xml
├── gradle.properties
└── src
│ ├── androidMain
│ └── AndroidManifest.xml
│ └── commonMain
│ └── kotlin
│ └── org
│ └── mobilenativefoundation
│ └── store
│ └── core5
│ ├── ExperimentalStoreApi.kt
│ ├── InsertionStrategy.kt
│ ├── KeyProvider.kt
│ ├── StoreData.kt
│ └── StoreKey.kt
├── gradle.properties
├── gradle
├── jacoco.gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── multicast
├── api
│ ├── android
│ │ └── multicast.api
│ └── jvm
│ │ └── multicast.api
├── build.gradle.kts
├── config
│ └── ktlint
│ │ └── baseline.xml
├── gradle.properties
└── src
│ ├── androidMain
│ └── AndroidManifest.xml
│ ├── commonMain
│ └── kotlin
│ │ └── org
│ │ └── mobilenativefoundation
│ │ └── store
│ │ └── multicast5
│ │ ├── Actor.kt
│ │ ├── ChannelManager.kt
│ │ ├── Multicaster.kt
│ │ ├── SharedFlowProducer.kt
│ │ └── StoreRealActor.kt
│ └── commonTest
│ └── kotlin
│ └── org
│ └── mobilenativefoundation
│ └── store
│ └── multicast5
│ └── StoreChannelManagerTests.kt
├── pull_request_template.md
├── renovate.json
├── rx2
├── api
│ └── rx2.api
├── build.gradle.kts
├── config
│ └── ktlint
│ │ └── baseline.xml
├── gradle.properties
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── org
│ │ └── mobilenativefoundation
│ │ └── store
│ │ └── rx2
│ │ ├── RxFetcher.kt
│ │ ├── RxSourceOfTruth.kt
│ │ ├── RxStore.kt
│ │ └── RxStoreBuilder.kt
│ └── test
│ └── kotlin
│ └── org
│ └── mobilenativefoundation
│ └── store
│ └── rx2
│ └── test
│ ├── FlowTestExt.kt
│ ├── HotRxSingleStoreTest.kt
│ ├── RxFlowableStoreTest.kt
│ ├── RxSingleStoreExtensionsTest.kt
│ └── RxSingleStoreTest.kt
├── settings.gradle
├── store
├── api
│ ├── android
│ │ └── store.api
│ └── jvm
│ │ └── store.api
├── build.gradle.kts
├── config
│ └── ktlint
│ │ └── baseline.xml
├── gradle.properties
└── src
│ ├── androidMain
│ └── AndroidManifest.xml
│ ├── commonMain
│ └── kotlin
│ │ └── org
│ │ └── mobilenativefoundation
│ │ └── store
│ │ └── store5
│ │ ├── Bookkeeper.kt
│ │ ├── Clear.kt
│ │ ├── Converter.kt
│ │ ├── Fetcher.kt
│ │ ├── FetcherResult.kt
│ │ ├── Logger.kt
│ │ ├── MemoryPolicy.kt
│ │ ├── MutableStore.kt
│ │ ├── MutableStoreBuilder.kt
│ │ ├── OnFetcherCompletion.kt
│ │ ├── OnUpdaterCompletion.kt
│ │ ├── Read.kt
│ │ ├── SourceOfTruth.kt
│ │ ├── Store.kt
│ │ ├── StoreBuilder.kt
│ │ ├── StoreDefaults.kt
│ │ ├── StoreReadRequest.kt
│ │ ├── StoreReadResponse.kt
│ │ ├── StoreWriteRequest.kt
│ │ ├── StoreWriteResponse.kt
│ │ ├── Updater.kt
│ │ ├── UpdaterResult.kt
│ │ ├── Validator.kt
│ │ ├── Write.kt
│ │ ├── impl
│ │ ├── DefaultLogger.kt
│ │ ├── FetcherController.kt
│ │ ├── OnStoreWriteCompletion.kt
│ │ ├── RealBookkeeper.kt
│ │ ├── RealMutableStore.kt
│ │ ├── RealMutableStoreBuilder.kt
│ │ ├── RealSourceOfTruth.kt
│ │ ├── RealStore.kt
│ │ ├── RealStoreBuilder.kt
│ │ ├── RealStoreWriteRequest.kt
│ │ ├── RealValidator.kt
│ │ ├── RefCountedResource.kt
│ │ ├── SourceOfTruthWithBarrier.kt
│ │ ├── extensions
│ │ │ ├── clock.kt
│ │ │ └── store.kt
│ │ └── operators
│ │ │ ├── FlowMerge.kt
│ │ │ └── MapIndexed.kt
│ │ ├── internal
│ │ ├── concurrent
│ │ │ ├── Lightswitch.kt
│ │ │ └── ThreadSafety.kt
│ │ ├── definition
│ │ │ ├── Timestamp.kt
│ │ │ └── WriteRequestQueue.kt
│ │ └── result
│ │ │ ├── EagerConflictResolutionResult.kt
│ │ │ └── StoreDelegateWriteResult.kt
│ │ └── storeBuilder.uml
│ └── commonTest
│ └── kotlin
│ └── org
│ └── mobilenativefoundation
│ └── store
│ └── store5
│ ├── ClearAllStoreTests.kt
│ ├── ClearStoreByKeyTests.kt
│ ├── FallbackTests.kt
│ ├── FetcherControllerTests.kt
│ ├── FetcherResponseTests.kt
│ ├── FlowStoreTests.kt
│ ├── HotFlowStoreTests.kt
│ ├── KeyTrackerTests.kt
│ ├── LocalOnlyTests.kt
│ ├── MapIndexedTests.kt
│ ├── SourceOfTruthErrorsTests.kt
│ ├── SourceOfTruthWithBarrierTests.kt
│ ├── StoreReadResponseTests.kt
│ ├── StoreWithInMemoryCacheTests.kt
│ ├── StreamWithoutSourceOfTruthTests.kt
│ ├── UpdaterTests.kt
│ ├── ValueFetcherTests.kt
│ ├── mutablestore
│ ├── RealMutableStoreTest.kt
│ └── util
│ │ ├── TestCache.kt
│ │ ├── TestConverter.kt
│ │ ├── TestFetcher.kt
│ │ ├── TestInMemoryBookkeeper.kt
│ │ ├── TestLogger.kt
│ │ ├── TestSourceOfTruth.kt
│ │ ├── TestStore.kt
│ │ ├── TestUpdater.kt
│ │ └── TestValidator.kt
│ └── util
│ ├── AsFlowable.kt
│ ├── FakeFetcher.kt
│ ├── InMemoryPersister.kt
│ ├── TestApi.kt
│ ├── TestStoreExt.kt
│ ├── fake
│ ├── NoteCollections.kt
│ ├── Notes.kt
│ ├── NotesApi.kt
│ ├── NotesBookkeeping.kt
│ ├── NotesConverterProvider.kt
│ ├── NotesDatabase.kt
│ ├── NotesKey.kt
│ ├── NotesUpdaterProvider.kt
│ ├── NotesValidator.kt
│ └── fallback
│ │ ├── HardcodedPages.kt
│ │ ├── Page.kt
│ │ ├── PagesDatabase.kt
│ │ ├── PrimaryPagesApi.kt
│ │ └── SecondaryPagesApi.kt
│ └── model
│ └── NoteData.kt
└── tooling
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── plugins
├── build.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── org
│ └── mobilenativefoundation
│ └── store
│ └── tooling
│ └── plugins
│ ├── AndroidConventionPlugin.kt
│ └── KotlinMultiplatformConventionPlugin.kt
└── settings.gradle.kts
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | range: 70..80
3 | round: down
4 | precision: 2
5 |
6 | comment:
7 | layout: diff, files
8 |
9 | ignore:
10 | - "**/fake"
11 | - "**/commonTest"
12 | - "**/androidTest"
13 | - "**/iOSTest"
14 | - "**/jsTest"
15 | - "**/jvmTest"
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Smartphone (please complete the following information):**
27 | - Device: [e.g. Pixel 3]
28 | - OS: [e.g. Android 10]
29 | - Store Version [e.g. 4.0.0]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[Feature Request] "
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/proposal.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Proposal
3 | about: Propose an API change
4 | title: "[Proposal] "
5 | ---
6 |
7 | # Proposal: [Title]
8 |
9 | Author(s): [GitHub username]
10 |
11 | Last updated: [Date]
12 |
13 | ## Abstract
14 |
15 | [A short summary of the proposal.]
16 |
17 | ## Background
18 |
19 | [An introduction of the necessary background and the problem being solved by the proposed change.]
20 |
21 | ## Proposal
22 |
23 | [A precise statement of the proposed change.]
24 |
25 | ## Rationale
26 |
27 | [A discussion of alternate approaches and the trade offs, advantages, and disadvantages of the specified approach.]
28 |
29 | ## Compatibility
30 |
31 | [A discussion of the change with regard to the current version of Store.]
32 |
33 | ## Implementation
34 |
35 | [A description of the steps in the implementation, who will do them, and when.]
36 |
37 | ## Open issues
38 |
39 | [A discussion of open issues relating to this proposal. This section may be omitted if there are none.]
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: gradle
4 | directory: "/"
5 | schedule:
6 | interval: daily
7 | open-pull-requests-limit: 1
8 | ignore:
9 | update-types: ["version-update:semver-major"]
10 |
--------------------------------------------------------------------------------
/.github/images/kotlin-foundation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/.github/images/kotlin-foundation.png
--------------------------------------------------------------------------------
/.github/images/mobile-native-foundation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/.github/images/mobile-native-foundation.png
--------------------------------------------------------------------------------
/.github/workflows/add_issue_to_project.yml:
--------------------------------------------------------------------------------
1 | name: Add Issue To Project
2 |
3 | on:
4 | issues:
5 | types:
6 | - opened
7 |
8 | jobs:
9 | add-issue-to-project:
10 | name: Add issue to project
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/add-to-project@main
14 | with:
15 | project-url: https://github.com/orgs/MobileNativeFoundation/projects/1
16 | github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT }}
17 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | build-and-test:
13 | runs-on: ubuntu-latest
14 | timeout-minutes: 30
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | api-level:
19 | - 29
20 | steps:
21 |
22 | - name: Checkout
23 | uses: actions/checkout@v3
24 | with:
25 | ref: ${{ github.head_ref || github.ref }}
26 | fetch-depth: 0
27 | persist-credentials: false
28 |
29 | - name: Set up JDK 11
30 | uses: actions/setup-java@v4
31 | with:
32 | distribution: 'zulu'
33 | java-version: '11'
34 |
35 | - name: Setup Gradle
36 | uses: gradle/gradle-build-action@v2
37 |
38 | - name: Grant execute permission for Gradlew
39 | run: chmod +x gradlew
40 |
41 | - name: Build and Test with Coverage
42 | run: ./gradlew clean build koverXmlReport --stacktrace
43 |
44 | - name: Upload Coverage to Codecov
45 | uses: codecov/codecov-action@v4
46 | with:
47 | token: ${{ secrets.CODECOV_TOKEN }}
48 | files: build/reports/kover/coverage.xml
49 | flags: unittests
50 | name: codecov-umbrella
51 | fail_ci_if_error: true
52 | verbose: true
53 |
54 | publish:
55 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'MobileNativeFoundation/Store'
56 | runs-on: macos-latest
57 | needs: build-and-test
58 | steps:
59 | - name: Checkout
60 | uses: actions/checkout@v3
61 |
62 | - name: Set up JDK 11
63 | uses: actions/setup-java@v4
64 | with:
65 | distribution: 'zulu'
66 | java-version: '11'
67 |
68 | - name: Grant execute permission for Gradlew
69 | run: chmod +x gradlew
70 |
71 | - name: Upload Artifacts to Maven Central
72 | run: ./gradlew publishAllPublicationsToMavenCentralRepository --no-daemon --no-parallel
73 | env:
74 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
75 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
76 | ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_KEY }}
77 | ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
78 |
79 | - name: Retrieve Version
80 | run: |
81 | echo "VERSION_NAME=$(cat gradle.properties | grep -w "VERSION_NAME" | cut -d'=' -f2)" >> $GITHUB_ENV
82 |
83 | - name: Publish Release
84 | run: ./gradlew closeAndReleaseRepository --no-daemon --no-parallel
85 | if: "!endsWith(env.VERSION_NAME, '-SNAPSHOT')"
86 | env:
87 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.SONATYPE_USERNAME }}
88 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.SONATYPE_PASSWORD }}
--------------------------------------------------------------------------------
/.github/workflows/create_swift_package.yml:
--------------------------------------------------------------------------------
1 | name: Create Swift Package
2 |
3 | on:
4 | workflow_dispatch:
5 | jobs:
6 | publish:
7 | uses: touchlab/KMMBridgeGithubWorkflow/.github/workflows/faktorybuildbranches.yml@v0.6
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # editor tempfiles
2 | *~
3 | # MacOS temp files
4 | .DS_Store
5 |
6 | # Built application files
7 | *.apk
8 | *.ap_
9 |
10 | # Files for the ART/Dalvik VM
11 | *.dex
12 |
13 | # Java class files
14 | *.class
15 |
16 | # Generated files
17 | bin/
18 | gen/
19 | out/
20 |
21 | # Gradle files
22 | .gradle/
23 | build/
24 |
25 | # Local configuration file (sdk path, etc)
26 | local.properties
27 |
28 | # Proguard folder generated by Eclipse
29 | proguard/
30 |
31 | # Log Files
32 | *.log
33 |
34 | # Android Studio Navigation editor temp files
35 | .navigation/
36 |
37 | # Android Studio captures folder
38 | captures/
39 |
40 | # Intellij
41 | *.iml
42 | .idea/
43 | .classpath
44 | .project
45 | .settings
46 |
47 | # Keystore files
48 | *.jks
49 |
50 | **/kover/html/
51 | *.podspec
52 | .kotlin/
53 | yarn.lock
54 |
55 | # Ignore coverage reports
56 | **/*/coverage.xml
57 | **/*/build/kover/
58 | **/*/build/reports/kover/
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Store
2 | Thanks for considering contributing to Store. This document provides guidelines and information about how you can contribute.
3 |
4 | ## Getting Started
5 | - **Fork the Repository**: Start by forking the [MobileNativeFoundation/Store](https://github.com/MobileNativeFoundation/Store) repository.
6 | - **Clone the Fork**: Clone your fork to your machine to start working on the changes.
7 |
8 | ## Contribution Workflow
9 | ### Reporting Issues
10 | - **Search Existing Issues**: Before creating a new issue, please do a search in existing issues to see if it has been reported or fixed.
11 | - **Create a Detailed Issue**: If you find a bug or have a feature request, please create an issue with a clear title and a detailed description.
12 | ### Submitting Changes
13 | - **Create a Branch**: Create a branch in your fork for your contribution.
14 | - **Make Your Changes**: Make your changes and commit them to your branch. Make sure to write clear, concise commit messages.
15 | - **Write Tests**: If you are adding new features or fixing bugs, write tests that cover your changes.
16 | - **Run the Tests**: Run the project's existing tests to ensure nothing is broken.
17 | - **Create a Pull Request**: Submit a PR to the main repository for review. Include a clear description of the changes and any relevant issue numbers.
18 | ### Code Review Process
19 | - **Wait for Review**: Maintainers will review your PR and might request changes.
20 | - **Make Requested Changes**: If changes are requested, make them and update your PR.
21 | - **Merge**: Once your PR is approved, a maintainer will merge it into the main codebase.
22 |
23 | ## Community Guidelines
24 | - **Be Respectful**: Treat everyone with respect. We strive to create a welcoming and inclusive environment.
25 | - **Follow the Code of Conduct**: Familiarize yourself with our [Code of Conduct](https://github.com/MobileNativeFoundation/Store/blob/main/CODE_OF_CONDUCT.md).
26 |
27 | ## Getting Help
28 | - **Join the Community**: If you have questions or need help, join our [Slack channel](https://kotlinlang.slack.com/archives/C06007Z01HU).
29 |
--------------------------------------------------------------------------------
/Images/friendly_robot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/Images/friendly_robot.png
--------------------------------------------------------------------------------
/Images/friendly_robot_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/Images/friendly_robot_icon.png
--------------------------------------------------------------------------------
/Images/store-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/Images/store-1.jpg
--------------------------------------------------------------------------------
/Images/store-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/Images/store-2.jpg
--------------------------------------------------------------------------------
/Images/store-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/Images/store-3.jpg
--------------------------------------------------------------------------------
/Images/store-4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/Images/store-4.jpg
--------------------------------------------------------------------------------
/Images/store-5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/Images/store-5.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Store5
4 |
5 | [](https://codecov.io/gh/MobileNativeFoundation/Store)
6 |
7 | #### Documentation
8 |
9 | Comprehensive guides, tutorials, and API reference: [store.mobilenativefoundation.org](https://store.mobilenativefoundation.org).
10 |
11 | #### Getting Started
12 |
13 | 1. Start with the [Quickstart](https://store.mobilenativefoundation.org/docs/quickstart) to build your first Store.
14 | 2. Dive into [Store Foundations](https://store.mobilenativefoundation.org/docs/concepts) to learn how Store works.
15 | 3. Check out [Handling CRUD](https://store.mobilenativefoundation.org/docs/use-cases/store5/setting-up-store-for-crud-operations) for an advanced guide on supporting create, read, update, and delete operations.
16 |
17 | #### Getting Help
18 |
19 | Join our community in the [#store](https://kotlinlang.slack.com/archives/C06007Z01HU) channel on the official Kotlin Slack.
20 |
21 | #### Getting Involved
22 |
23 | Store has a vibrant community of contributors. We welcome contributions of all kinds. Please see our [Contributing Guidelines](CONTRIBUTING.md) for more information on how to get involved.
24 |
25 | #### Backed By
26 |
27 |
28 |

29 |

30 |
31 |
32 | #### License
33 |
34 | ```text
35 | Copyright (c) 2024 Mobile Native Foundation.
36 | Licensed under the Apache License, Version 2.0 (the "License");
37 | you may not use this file except in compliance with the License.
38 | ```
39 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | Releasing
2 | ========
3 |
4 | 1. Change the version in top level `gradle.properties` to a non-SNAPSHOT version.
5 | 2. Update the `cocoapods` version in `build.gradle.kts` in `:store`.
6 | 3. Modify `create_swift_package.yml` workflow.
7 | * https://github.com/MobileNativeFoundation/Store/blob/e526400cdf51aa2f78b6b7e9e87f4a6845e6dcea/.github/workflows/create_swift_package.yml
8 | 4. Update the `CHANGELOG.md` for the impending release.
9 | 5. Update the `README.md` with the new version.
10 | 6. `git commit -sam "Prepare for release X.Y.Z."` (where X.Y.Z is the new version)
11 | 7. `git tag -a X.Y.X -m "Version X.Y.Z"` (where X.Y.Z is the new version)
12 | * Run `git tag` to verify it.
13 | 8. `git push && git push --tags`
14 | * This should be pushed to your fork.
15 | 9. Create a PR with this commit and merge it.
16 | 10. Update the top level `build.gradle` to the next SNAPSHOT version.
17 | 11. Modify `create_swift_package.yml` workflow to only run manually.
18 | * https://github.com/MobileNativeFoundation/Store/blob/de9ed1764408eeaafe5e58fe602205c875a8b0b0/.github/workflows/create_swift_package.yml
19 | 12. `git commit -am "Prepare next development version."`
20 | 13. Create a PR with this commit and merge it.
21 | 14. Login to Sonatype to promote the artifacts https://central.sonatype.org/pages/releasing-the-deployment.html
22 | * This part is automated. If it fails in CI, follow the steps below.
23 | * Click on Staging Repositories under Build Promotion
24 | * Select all the Repositories that contain the content you want to release
25 | * Click on Close and refresh until the Release button is active
26 | * Click Release and submit
27 | 15. Update the sample module's `build.gradle` to point to the newly released version. (It may take ~2 hours for artifact to be available after release)
28 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.ktlint)
3 | id("com.diffplug.spotless") version "6.4.1"
4 | }
5 |
6 | buildscript {
7 | repositories {
8 | mavenCentral()
9 | gradlePluginPortal()
10 | google()
11 | }
12 |
13 | dependencies {
14 | classpath(libs.android.gradle.plugin)
15 | classpath(libs.kotlin.gradle.plugin)
16 | classpath(libs.kotlin.serialization.plugin)
17 | classpath(libs.dokka.gradle.plugin)
18 | classpath(libs.ktlint.gradle.plugin)
19 | classpath(libs.jacoco.gradle.plugin)
20 | classpath(libs.maven.publish.plugin)
21 | classpath(libs.atomic.fu.gradle.plugin)
22 | classpath(libs.kmmBridge.gradle.plugin)
23 | classpath(libs.binary.compatibility.validator)
24 | }
25 | }
26 |
27 | allprojects {
28 | repositories {
29 | mavenCentral()
30 | google()
31 | }
32 | }
33 |
34 | subprojects {
35 | apply(plugin = "org.jlleitschuh.gradle.ktlint")
36 | apply(plugin = "com.diffplug.spotless")
37 |
38 | ktlint {
39 | disabledRules.add("import-ordering")
40 | }
41 |
42 | spotless {
43 | kotlin {
44 | target("src/**/*.kt")
45 | }
46 | }
47 | }
48 |
49 | tasks {
50 | withType {
51 | kotlinOptions {
52 | jvmTarget = "11"
53 | }
54 | }
55 |
56 | withType().configureEach {
57 | sourceCompatibility = JavaVersion.VERSION_11.name
58 | targetCompatibility = JavaVersion.VERSION_11.name
59 | }
60 | }
61 |
62 | // Workaround for https://youtrack.jetbrains.com/issue/KT-62040
63 | tasks.getByName("wrapper")
64 |
--------------------------------------------------------------------------------
/cache/README.md:
--------------------------------------------------------------------------------
1 | # Cache
2 |
3 | Store depends on a subset of [Guava](https://github.com/google/guava).
4 | This is a shaded artifact that is Kotlin Multiplatform compatible.
5 |
6 | ## Usage
7 |
8 | ```kotlin
9 | implementation("org.mobilenativefoundation.store:cache:${STORE_VERSION}")
10 | ```
11 |
12 | ## Implementation
13 |
14 | ### Model the key
15 |
16 | ```kotlin
17 | data class Key(
18 | val id: String
19 | )
20 | ```
21 |
22 | ### Model the value
23 |
24 | ```kotlin
25 | data class Post(
26 | val title: String
27 | )
28 | ```
29 |
30 | ### Build the cache
31 |
32 | ```kotlin
33 | val cache = CacheBuilder()
34 | .maximumSize(100)
35 | .expireAfterWrite(1.day)
36 | .build()
37 | ```
38 |
39 | ## See Also
40 |
41 | https://github.com/google/guava/wiki/CachesExplained
42 |
43 | ## License
44 |
45 | ```text
46 | Copyright (c) 2017 The New York Times Company
47 |
48 | Copyright (c) 2010 The Guava Authors
49 |
50 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this library except in
51 | compliance with the License. You may obtain a copy of the License at
52 |
53 | www.apache.org/licenses/LICENSE-2.0
54 |
55 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an
56 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
57 | language governing permissions and limitations under the License.
58 | ```
59 |
--------------------------------------------------------------------------------
/cache/api/android/cache.api:
--------------------------------------------------------------------------------
1 | public final class org/mobilenativefoundation/store/cache/BuildConfig {
2 | public static final field BUILD_TYPE Ljava/lang/String;
3 | public static final field DEBUG Z
4 | public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
5 | public fun ()V
6 | }
7 |
8 | public abstract interface class org/mobilenativefoundation/store/cache5/Cache {
9 | public abstract fun getAllPresent ()Ljava/util/Map;
10 | public abstract fun getAllPresent (Ljava/util/List;)Ljava/util/Map;
11 | public abstract fun getIfPresent (Ljava/lang/Object;)Ljava/lang/Object;
12 | public abstract fun getOrPut (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
13 | public abstract fun invalidate (Ljava/lang/Object;)V
14 | public abstract fun invalidateAll ()V
15 | public abstract fun invalidateAll (Ljava/util/List;)V
16 | public abstract fun put (Ljava/lang/Object;Ljava/lang/Object;)V
17 | public abstract fun putAll (Ljava/util/Map;)V
18 | public abstract fun size ()J
19 | }
20 |
21 | public final class org/mobilenativefoundation/store/cache5/Cache$DefaultImpls {
22 | public static fun getAllPresent (Lorg/mobilenativefoundation/store/cache5/Cache;)Ljava/util/Map;
23 | }
24 |
25 | public final class org/mobilenativefoundation/store/cache5/CacheBuilder {
26 | public static final field Companion Lorg/mobilenativefoundation/store/cache5/CacheBuilder$Companion;
27 | public fun ()V
28 | public final fun build ()Lorg/mobilenativefoundation/store/cache5/Cache;
29 | public final fun concurrencyLevel (Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
30 | public final fun expireAfterAccess-LRDsOJo (J)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
31 | public final fun expireAfterWrite-LRDsOJo (J)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
32 | public final fun maximumSize (J)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
33 | public final fun ticker (Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
34 | public final fun weigher (JLkotlin/jvm/functions/Function2;)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
35 | }
36 |
37 | public final class org/mobilenativefoundation/store/cache5/CacheBuilder$Companion {
38 | }
39 |
40 | public final class org/mobilenativefoundation/store/cache5/StoreMultiCache : org/mobilenativefoundation/store/cache5/Cache {
41 | public static final field Companion Lorg/mobilenativefoundation/store/cache5/StoreMultiCache$Companion;
42 | public fun (Lorg/mobilenativefoundation/store/core5/KeyProvider;Lorg/mobilenativefoundation/store/cache5/Cache;Lorg/mobilenativefoundation/store/cache5/Cache;)V
43 | public synthetic fun (Lorg/mobilenativefoundation/store/core5/KeyProvider;Lorg/mobilenativefoundation/store/cache5/Cache;Lorg/mobilenativefoundation/store/cache5/Cache;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
44 | public fun getAllPresent ()Ljava/util/Map;
45 | public fun getAllPresent (Ljava/util/List;)Ljava/util/Map;
46 | public synthetic fun getIfPresent (Ljava/lang/Object;)Ljava/lang/Object;
47 | public fun getIfPresent (Lorg/mobilenativefoundation/store/core5/StoreKey;)Lorg/mobilenativefoundation/store/core5/StoreData;
48 | public synthetic fun getOrPut (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
49 | public fun getOrPut (Lorg/mobilenativefoundation/store/core5/StoreKey;Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/core5/StoreData;
50 | public synthetic fun invalidate (Ljava/lang/Object;)V
51 | public fun invalidate (Lorg/mobilenativefoundation/store/core5/StoreKey;)V
52 | public fun invalidateAll ()V
53 | public fun invalidateAll (Ljava/util/List;)V
54 | public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)V
55 | public fun put (Lorg/mobilenativefoundation/store/core5/StoreKey;Lorg/mobilenativefoundation/store/core5/StoreData;)V
56 | public fun putAll (Ljava/util/Map;)V
57 | public fun size ()J
58 | }
59 |
60 | public final class org/mobilenativefoundation/store/cache5/StoreMultiCache$Companion {
61 | public final fun invalidKeyErrorMessage (Ljava/lang/Object;)Ljava/lang/String;
62 | }
63 |
64 | public final class org/mobilenativefoundation/store/cache5/StoreMultiCacheAccessor {
65 | public fun (Lorg/mobilenativefoundation/store/cache5/Cache;Lorg/mobilenativefoundation/store/cache5/Cache;)V
66 | public final fun getAllPresent ()Ljava/util/Map;
67 | public final fun getCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;)Lorg/mobilenativefoundation/store/core5/StoreData$Collection;
68 | public final fun getSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;)Lorg/mobilenativefoundation/store/core5/StoreData$Single;
69 | public final fun invalidateAll ()V
70 | public final fun invalidateCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;)Z
71 | public final fun invalidateSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;)Z
72 | public final fun putCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;Lorg/mobilenativefoundation/store/core5/StoreData$Collection;)Z
73 | public final fun putSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;Lorg/mobilenativefoundation/store/core5/StoreData$Single;)Z
74 | public final fun size ()J
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/cache/api/jvm/cache.api:
--------------------------------------------------------------------------------
1 | public abstract interface class org/mobilenativefoundation/store/cache5/Cache {
2 | public abstract fun getAllPresent ()Ljava/util/Map;
3 | public abstract fun getAllPresent (Ljava/util/List;)Ljava/util/Map;
4 | public abstract fun getIfPresent (Ljava/lang/Object;)Ljava/lang/Object;
5 | public abstract fun getOrPut (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
6 | public abstract fun invalidate (Ljava/lang/Object;)V
7 | public abstract fun invalidateAll ()V
8 | public abstract fun invalidateAll (Ljava/util/List;)V
9 | public abstract fun put (Ljava/lang/Object;Ljava/lang/Object;)V
10 | public abstract fun putAll (Ljava/util/Map;)V
11 | public abstract fun size ()J
12 | }
13 |
14 | public final class org/mobilenativefoundation/store/cache5/Cache$DefaultImpls {
15 | public static fun getAllPresent (Lorg/mobilenativefoundation/store/cache5/Cache;)Ljava/util/Map;
16 | }
17 |
18 | public final class org/mobilenativefoundation/store/cache5/CacheBuilder {
19 | public static final field Companion Lorg/mobilenativefoundation/store/cache5/CacheBuilder$Companion;
20 | public fun ()V
21 | public final fun build ()Lorg/mobilenativefoundation/store/cache5/Cache;
22 | public final fun concurrencyLevel (Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
23 | public final fun expireAfterAccess-LRDsOJo (J)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
24 | public final fun expireAfterWrite-LRDsOJo (J)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
25 | public final fun maximumSize (J)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
26 | public final fun ticker (Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
27 | public final fun weigher (JLkotlin/jvm/functions/Function2;)Lorg/mobilenativefoundation/store/cache5/CacheBuilder;
28 | }
29 |
30 | public final class org/mobilenativefoundation/store/cache5/CacheBuilder$Companion {
31 | }
32 |
33 | public final class org/mobilenativefoundation/store/cache5/StoreMultiCache : org/mobilenativefoundation/store/cache5/Cache {
34 | public static final field Companion Lorg/mobilenativefoundation/store/cache5/StoreMultiCache$Companion;
35 | public fun (Lorg/mobilenativefoundation/store/core5/KeyProvider;Lorg/mobilenativefoundation/store/cache5/Cache;Lorg/mobilenativefoundation/store/cache5/Cache;)V
36 | public synthetic fun (Lorg/mobilenativefoundation/store/core5/KeyProvider;Lorg/mobilenativefoundation/store/cache5/Cache;Lorg/mobilenativefoundation/store/cache5/Cache;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
37 | public fun getAllPresent ()Ljava/util/Map;
38 | public fun getAllPresent (Ljava/util/List;)Ljava/util/Map;
39 | public synthetic fun getIfPresent (Ljava/lang/Object;)Ljava/lang/Object;
40 | public fun getIfPresent (Lorg/mobilenativefoundation/store/core5/StoreKey;)Lorg/mobilenativefoundation/store/core5/StoreData;
41 | public synthetic fun getOrPut (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
42 | public fun getOrPut (Lorg/mobilenativefoundation/store/core5/StoreKey;Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/core5/StoreData;
43 | public synthetic fun invalidate (Ljava/lang/Object;)V
44 | public fun invalidate (Lorg/mobilenativefoundation/store/core5/StoreKey;)V
45 | public fun invalidateAll ()V
46 | public fun invalidateAll (Ljava/util/List;)V
47 | public synthetic fun put (Ljava/lang/Object;Ljava/lang/Object;)V
48 | public fun put (Lorg/mobilenativefoundation/store/core5/StoreKey;Lorg/mobilenativefoundation/store/core5/StoreData;)V
49 | public fun putAll (Ljava/util/Map;)V
50 | public fun size ()J
51 | }
52 |
53 | public final class org/mobilenativefoundation/store/cache5/StoreMultiCache$Companion {
54 | public final fun invalidKeyErrorMessage (Ljava/lang/Object;)Ljava/lang/String;
55 | }
56 |
57 | public final class org/mobilenativefoundation/store/cache5/StoreMultiCacheAccessor {
58 | public fun (Lorg/mobilenativefoundation/store/cache5/Cache;Lorg/mobilenativefoundation/store/cache5/Cache;)V
59 | public final fun getAllPresent ()Ljava/util/Map;
60 | public final fun getCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;)Lorg/mobilenativefoundation/store/core5/StoreData$Collection;
61 | public final fun getSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;)Lorg/mobilenativefoundation/store/core5/StoreData$Single;
62 | public final fun invalidateAll ()V
63 | public final fun invalidateCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;)Z
64 | public final fun invalidateSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;)Z
65 | public final fun putCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;Lorg/mobilenativefoundation/store/core5/StoreData$Collection;)Z
66 | public final fun putSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;Lorg/mobilenativefoundation/store/core5/StoreData$Single;)Z
67 | public final fun size ()J
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/cache/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.mobilenativefoundation.store.multiplatform")
3 | }
4 |
5 | kotlin {
6 |
7 | sourceSets {
8 | val commonMain by getting {
9 | dependencies {
10 | api(libs.kotlinx.atomic.fu)
11 | api(projects.core)
12 | implementation(libs.kotlinx.coroutines.core)
13 | }
14 | }
15 | val commonTest by getting {
16 | dependencies {
17 | implementation(libs.junit)
18 | implementation(libs.kotlinx.coroutines.test)
19 | }
20 | }
21 | }
22 | }
23 |
24 | android {
25 | namespace = "org.mobilenativefoundation.store.cache"
26 | }
27 |
--------------------------------------------------------------------------------
/cache/config/ktlint/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/cache/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=org.mobilenativefoundation.store
2 | POM_ARTIFACT_ID=cache5
3 | POM_PACKAGING=jar
--------------------------------------------------------------------------------
/cache/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/Cache.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.cache5
2 |
3 | interface Cache {
4 | /**
5 | * @return [Value] associated with [key] or `null` if there is no cached value for [key].
6 | */
7 | fun getIfPresent(key: Key): Value?
8 |
9 | /**
10 | * @return [Value] associated with [key], obtaining the value from [valueProducer] if necessary.
11 | * No observable state associated with this cache is modified until loading completes.
12 | * @param [valueProducer] Must not return `null`. It may either return a non-null value or throw an exception.
13 | * @throws ExecutionExeption If a checked exception was thrown while loading the value.
14 | * @throws UncheckedExecutionException If an unchecked exception was thrown while loading the value.
15 | * @throws ExecutionError If an error was thrown while loading the value.
16 | */
17 | fun getOrPut(
18 | key: Key,
19 | valueProducer: () -> Value,
20 | ): Value
21 |
22 | /**
23 | * @return Map of the [Value] associated with each [Key] in [keys]. Returned map only contains entries already present in the cache.
24 | * The default implementation provided here throws a [NotImplementedError] to maintain backward compatibility for existing implementations.
25 | */
26 | fun getAllPresent(keys: List<*>): Map
27 |
28 | /**
29 | * @return Map of the [Value] associated with each [Key] in the cache.
30 | */
31 | fun getAllPresent(): Map = throw NotImplementedError()
32 |
33 | /**
34 | * Associates [value] with [key].
35 | * If the cache previously contained a value associated with [key], the old value is replaced by [value].
36 | * Prefer [getOrPut] when using the conventional "If cached, then return. Otherwise create, cache, and then return" pattern.
37 | */
38 | fun put(
39 | key: Key,
40 | value: Value,
41 | )
42 |
43 | /**
44 | * Copies all of the mappings from the specified map to the cache. The effect of this call is
45 | * equivalent to that of calling [put] on this map once for each mapping from [Key] to [Value] in the specified map.
46 | * The behavior of this operation is undefined if the specified map is modified while the operation is in progress.
47 | */
48 | fun putAll(map: Map)
49 |
50 | /**
51 | * Discards any cached value associated with [key].
52 | */
53 | fun invalidate(key: Key)
54 |
55 | /**
56 | * Discards any cached value associated for [keys].
57 | */
58 | fun invalidateAll(keys: List)
59 |
60 | /**
61 | * Discards all entries in the cache.
62 | */
63 | fun invalidateAll()
64 |
65 | /**
66 | * @return Approximate number of entries in the cache.
67 | */
68 | fun size(): Long
69 | }
70 |
--------------------------------------------------------------------------------
/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/CacheBuilder.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.cache5
2 |
3 | import kotlin.time.Duration
4 |
5 | class CacheBuilder {
6 | internal var concurrencyLevel = 4
7 | private set
8 | internal val initialCapacity = 16
9 | internal var maximumSize = UNSET
10 | private set
11 | internal var maximumWeight = UNSET
12 | private set
13 | internal var expireAfterAccess: Duration = Duration.INFINITE
14 | private set
15 | internal var expireAfterWrite: Duration = Duration.INFINITE
16 | private set
17 | internal var weigher: Weigher? = null
18 | private set
19 | internal var ticker: Ticker? = null
20 | private set
21 |
22 | fun concurrencyLevel(producer: () -> Int): CacheBuilder =
23 | apply {
24 | concurrencyLevel = producer.invoke()
25 | }
26 |
27 | fun maximumSize(maximumSize: Long): CacheBuilder =
28 | apply {
29 | if (maximumSize < 0) {
30 | throw IllegalArgumentException("Maximum size must be non-negative.")
31 | }
32 | this.maximumSize = maximumSize
33 | }
34 |
35 | fun expireAfterAccess(duration: Duration): CacheBuilder =
36 | apply {
37 | if (duration.isNegative()) {
38 | throw IllegalArgumentException("Duration must be non-negative.")
39 | }
40 | expireAfterAccess = duration
41 | }
42 |
43 | fun expireAfterWrite(duration: Duration): CacheBuilder =
44 | apply {
45 | if (duration.isNegative()) {
46 | throw IllegalArgumentException("Duration must be non-negative.")
47 | }
48 | expireAfterWrite = duration
49 | }
50 |
51 | fun ticker(ticker: Ticker): CacheBuilder =
52 | apply {
53 | this.ticker = ticker
54 | }
55 |
56 | fun weigher(
57 | maximumWeight: Long,
58 | weigher: Weigher,
59 | ): CacheBuilder =
60 | apply {
61 | if (maximumWeight < 0) {
62 | throw IllegalArgumentException("Maximum weight must be non-negative.")
63 | }
64 |
65 | this.maximumWeight = maximumWeight
66 | this.weigher = weigher
67 | }
68 |
69 | fun build(): Cache {
70 | if (maximumSize != -1L && weigher != null) {
71 | throw IllegalStateException("Maximum size cannot be combined with weigher.")
72 | }
73 | return LocalCache.LocalManualCache(this)
74 | }
75 |
76 | companion object {
77 | private const val UNSET = -1L
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/MonotonicTicker.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.cache5
2 |
3 | import kotlin.time.ExperimentalTime
4 | import kotlin.time.TimeSource
5 |
6 | @OptIn(ExperimentalTime::class)
7 | internal val MonotonicTicker: Ticker = TimeSource.Monotonic.markNow().let { timeMark -> { timeMark.elapsedNow().inWholeNanoseconds } }
8 |
--------------------------------------------------------------------------------
/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/RemovalCause.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.cache5
2 |
3 | /**
4 | * The reason why a cached entry was removed.
5 | * @param wasEvicted True if entry removal was automatic due to eviction. That is, the cause of removal is neither [EXPLICIT] or [REPLACED].
6 | * @author Charles Fry
7 | * @since 10.0
8 | */
9 | internal enum class RemovalCause(val wasEvicted: Boolean) {
10 | EXPLICIT(false),
11 | REPLACED(false),
12 | COLLECTED(true),
13 | EXPIRED(true),
14 | SIZE(true),
15 | }
16 |
--------------------------------------------------------------------------------
/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/Ticker.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.cache5
2 |
3 | /**
4 | * @return Number of nanoseconds elapsed since the ticker's fixed point of reference.
5 | */
6 | typealias Ticker = () -> Long
7 |
--------------------------------------------------------------------------------
/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/Weigher.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.cache5
2 |
3 | /**
4 | * @return Weight of a cache entry. Must be non-negative. There is no unit for entry weights. Rather, they are simply relative to each other.
5 | */
6 | typealias Weigher = (key: Key, value: Value) -> Int
7 |
--------------------------------------------------------------------------------
/cache/src/commonTest/kotlin/org/mobilenativefoundation/store/cache5/CacheTests.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.cache5
2 |
3 | import kotlinx.coroutines.test.runTest
4 | import kotlin.test.Ignore
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.time.Duration.Companion.milliseconds
8 |
9 | class CacheTests {
10 | private val cache: Cache = CacheBuilder().build()
11 |
12 | @Test
13 | fun getIfPresent() {
14 | cache.put("key", "value")
15 | assertEquals("value", cache.getIfPresent("key"))
16 | }
17 |
18 | @Test
19 | fun getOrPut() {
20 | assertEquals("value", cache.getOrPut("key") { "value" })
21 | }
22 |
23 | @Test
24 | fun getAllPresent() {
25 | cache.put("key1", "value1")
26 | cache.put("key2", "value2")
27 | assertEquals(mapOf("key1" to "value1", "key2" to "value2"), cache.getAllPresent(listOf("key1", "key2")))
28 | assertEquals(mapOf("key1" to "value1", "key2" to "value2"), cache.getAllPresent())
29 | }
30 |
31 | @Ignore // Not implemented yet
32 | @Test
33 | fun putAll() {
34 | cache.putAll(mapOf("key1" to "value1", "key2" to "value2"))
35 | assertEquals(mapOf("key1" to "value1", "key2" to "value2"), cache.getAllPresent(listOf("key1", "key2")))
36 | }
37 |
38 | @Test
39 | fun invalidate() {
40 | cache.put("key", "value")
41 | cache.invalidate("key")
42 | assertEquals(null, cache.getIfPresent("key"))
43 | }
44 |
45 | @Ignore // Not implemented yet
46 | @Test
47 | fun invalidateAll() {
48 | cache.put("key1", "value1")
49 | cache.put("key2", "value2")
50 | cache.invalidateAll(listOf("key1", "key2"))
51 | assertEquals(null, cache.getIfPresent("key1"))
52 | assertEquals(null, cache.getIfPresent("key2"))
53 | }
54 |
55 | @Ignore // Not implemented yet
56 | @Test
57 | fun size() {
58 | cache.put("key1", "value1")
59 | cache.put("key2", "value2")
60 | assertEquals(2, cache.size())
61 | }
62 |
63 | @Test
64 | fun maximumSize() {
65 | val cache = CacheBuilder().maximumSize(1).build()
66 | cache.put("key1", "value1")
67 | cache.put("key2", "value2")
68 | assertEquals(null, cache.getIfPresent("key1"))
69 | assertEquals("value2", cache.getIfPresent("key2"))
70 | }
71 |
72 | @Test
73 | fun maximumWeight() {
74 | val cache = CacheBuilder().weigher(399) { _, _ -> 100 }.build()
75 | cache.put("key1", "value1")
76 | cache.put("key2", "value2")
77 | assertEquals(null, cache.getIfPresent("key1"))
78 | assertEquals("value2", cache.getIfPresent("key2"))
79 | }
80 |
81 | @Test
82 | fun expireAfterAccess() =
83 | runTest {
84 | var timeNs = 0L
85 | val cache = CacheBuilder().expireAfterAccess(100.milliseconds).ticker { timeNs }.build()
86 | cache.put("key", "value")
87 |
88 | timeNs += 50.milliseconds.inWholeNanoseconds
89 | assertEquals("value", cache.getIfPresent("key"))
90 |
91 | timeNs += 100.milliseconds.inWholeNanoseconds
92 | assertEquals(null, cache.getIfPresent("key"))
93 | }
94 |
95 | @Test
96 | fun expireAfterWrite() =
97 | runTest {
98 | var timeNs = 0L
99 | val cache = CacheBuilder().expireAfterWrite(100.milliseconds).ticker { timeNs }.build()
100 | cache.put("key", "value")
101 |
102 | timeNs += 50.milliseconds.inWholeNanoseconds
103 | assertEquals("value", cache.getIfPresent("key"))
104 |
105 | timeNs += 50.milliseconds.inWholeNanoseconds
106 | assertEquals(null, cache.getIfPresent("key"))
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/config/ktlint/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/api/android/core.api:
--------------------------------------------------------------------------------
1 | public final class org/mobilenativefoundation/store/core/BuildConfig {
2 | public static final field BUILD_TYPE Ljava/lang/String;
3 | public static final field DEBUG Z
4 | public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
5 | public fun ()V
6 | }
7 |
8 | public abstract interface annotation class org/mobilenativefoundation/store/core5/ExperimentalStoreApi : java/lang/annotation/Annotation {
9 | }
10 |
11 | public final class org/mobilenativefoundation/store/core5/InsertionStrategy : java/lang/Enum {
12 | public static final field APPEND Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
13 | public static final field PREPEND Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
14 | public static final field REPLACE Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
15 | public static fun getEntries ()Lkotlin/enums/EnumEntries;
16 | public static fun valueOf (Ljava/lang/String;)Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
17 | public static fun values ()[Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
18 | }
19 |
20 | public abstract interface class org/mobilenativefoundation/store/core5/KeyProvider {
21 | public abstract fun fromCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;Lorg/mobilenativefoundation/store/core5/StoreData$Single;)Lorg/mobilenativefoundation/store/core5/StoreKey$Single;
22 | public abstract fun fromSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;Lorg/mobilenativefoundation/store/core5/StoreData$Single;)Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;
23 | }
24 |
25 | public abstract interface class org/mobilenativefoundation/store/core5/StoreData {
26 | }
27 |
28 | public abstract interface class org/mobilenativefoundation/store/core5/StoreData$Collection : org/mobilenativefoundation/store/core5/StoreData {
29 | public abstract fun copyWith (Ljava/util/List;)Lorg/mobilenativefoundation/store/core5/StoreData$Collection;
30 | public abstract fun getItems ()Ljava/util/List;
31 | public abstract fun insertItems (Lorg/mobilenativefoundation/store/core5/InsertionStrategy;Ljava/util/List;)Lorg/mobilenativefoundation/store/core5/StoreData$Collection;
32 | }
33 |
34 | public abstract interface class org/mobilenativefoundation/store/core5/StoreData$Single : org/mobilenativefoundation/store/core5/StoreData {
35 | public abstract fun getId ()Ljava/lang/Object;
36 | }
37 |
38 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey {
39 | }
40 |
41 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Collection : org/mobilenativefoundation/store/core5/StoreKey {
42 | public abstract fun getInsertionStrategy ()Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
43 | }
44 |
45 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Collection$Cursor : org/mobilenativefoundation/store/core5/StoreKey$Collection {
46 | public abstract fun getCursor ()Ljava/lang/Object;
47 | public abstract fun getFilters ()Ljava/util/List;
48 | public abstract fun getSize ()I
49 | public abstract fun getSort ()Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
50 | }
51 |
52 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Collection$Page : org/mobilenativefoundation/store/core5/StoreKey$Collection {
53 | public abstract fun getFilters ()Ljava/util/List;
54 | public abstract fun getPage ()I
55 | public abstract fun getSize ()I
56 | public abstract fun getSort ()Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
57 | }
58 |
59 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Filter {
60 | public abstract fun invoke (Ljava/util/List;)Ljava/util/List;
61 | }
62 |
63 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Single : org/mobilenativefoundation/store/core5/StoreKey {
64 | public abstract fun getId ()Ljava/lang/Object;
65 | }
66 |
67 | public final class org/mobilenativefoundation/store/core5/StoreKey$Sort : java/lang/Enum {
68 | public static final field ALPHABETICAL Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
69 | public static final field NEWEST Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
70 | public static final field OLDEST Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
71 | public static final field REVERSE_ALPHABETICAL Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
72 | public static fun getEntries ()Lkotlin/enums/EnumEntries;
73 | public static fun valueOf (Ljava/lang/String;)Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
74 | public static fun values ()[Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/core/api/jvm/core.api:
--------------------------------------------------------------------------------
1 | public abstract interface annotation class org/mobilenativefoundation/store/core5/ExperimentalStoreApi : java/lang/annotation/Annotation {
2 | }
3 |
4 | public final class org/mobilenativefoundation/store/core5/InsertionStrategy : java/lang/Enum {
5 | public static final field APPEND Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
6 | public static final field PREPEND Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
7 | public static final field REPLACE Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
8 | public static fun getEntries ()Lkotlin/enums/EnumEntries;
9 | public static fun valueOf (Ljava/lang/String;)Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
10 | public static fun values ()[Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
11 | }
12 |
13 | public abstract interface class org/mobilenativefoundation/store/core5/KeyProvider {
14 | public abstract fun fromCollection (Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;Lorg/mobilenativefoundation/store/core5/StoreData$Single;)Lorg/mobilenativefoundation/store/core5/StoreKey$Single;
15 | public abstract fun fromSingle (Lorg/mobilenativefoundation/store/core5/StoreKey$Single;Lorg/mobilenativefoundation/store/core5/StoreData$Single;)Lorg/mobilenativefoundation/store/core5/StoreKey$Collection;
16 | }
17 |
18 | public abstract interface class org/mobilenativefoundation/store/core5/StoreData {
19 | }
20 |
21 | public abstract interface class org/mobilenativefoundation/store/core5/StoreData$Collection : org/mobilenativefoundation/store/core5/StoreData {
22 | public abstract fun copyWith (Ljava/util/List;)Lorg/mobilenativefoundation/store/core5/StoreData$Collection;
23 | public abstract fun getItems ()Ljava/util/List;
24 | public abstract fun insertItems (Lorg/mobilenativefoundation/store/core5/InsertionStrategy;Ljava/util/List;)Lorg/mobilenativefoundation/store/core5/StoreData$Collection;
25 | }
26 |
27 | public abstract interface class org/mobilenativefoundation/store/core5/StoreData$Single : org/mobilenativefoundation/store/core5/StoreData {
28 | public abstract fun getId ()Ljava/lang/Object;
29 | }
30 |
31 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey {
32 | }
33 |
34 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Collection : org/mobilenativefoundation/store/core5/StoreKey {
35 | public abstract fun getInsertionStrategy ()Lorg/mobilenativefoundation/store/core5/InsertionStrategy;
36 | }
37 |
38 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Collection$Cursor : org/mobilenativefoundation/store/core5/StoreKey$Collection {
39 | public abstract fun getCursor ()Ljava/lang/Object;
40 | public abstract fun getFilters ()Ljava/util/List;
41 | public abstract fun getSize ()I
42 | public abstract fun getSort ()Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
43 | }
44 |
45 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Collection$Page : org/mobilenativefoundation/store/core5/StoreKey$Collection {
46 | public abstract fun getFilters ()Ljava/util/List;
47 | public abstract fun getPage ()I
48 | public abstract fun getSize ()I
49 | public abstract fun getSort ()Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
50 | }
51 |
52 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Filter {
53 | public abstract fun invoke (Ljava/util/List;)Ljava/util/List;
54 | }
55 |
56 | public abstract interface class org/mobilenativefoundation/store/core5/StoreKey$Single : org/mobilenativefoundation/store/core5/StoreKey {
57 | public abstract fun getId ()Ljava/lang/Object;
58 | }
59 |
60 | public final class org/mobilenativefoundation/store/core5/StoreKey$Sort : java/lang/Enum {
61 | public static final field ALPHABETICAL Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
62 | public static final field NEWEST Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
63 | public static final field OLDEST Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
64 | public static final field REVERSE_ALPHABETICAL Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
65 | public static fun getEntries ()Lkotlin/enums/EnumEntries;
66 | public static fun valueOf (Ljava/lang/String;)Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
67 | public static fun values ()[Lorg/mobilenativefoundation/store/core5/StoreKey$Sort;
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.mobilenativefoundation.store.multiplatform")
3 | }
4 |
5 | kotlin {
6 |
7 | sourceSets {
8 | val commonMain by getting {
9 | dependencies {
10 | implementation(libs.kotlin.stdlib)
11 | }
12 | }
13 | }
14 | }
15 |
16 | android {
17 | namespace = "org.mobilenativefoundation.store.core"
18 | }
19 |
--------------------------------------------------------------------------------
/core/config/ktlint/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=org.mobilenativefoundation.store
2 | POM_ARTIFACT_ID=core5
3 | POM_PACKAGING=jar
--------------------------------------------------------------------------------
/core/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/org/mobilenativefoundation/store/core5/ExperimentalStoreApi.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.core5
2 |
3 | /**
4 | * Marks declarations that are still **experimental** in store API.
5 | * Declarations marked with this annotation are unstable and subject to change.
6 | */
7 | @MustBeDocumented
8 | @Retention(value = AnnotationRetention.BINARY)
9 | @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
10 | annotation class ExperimentalStoreApi
11 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/org/mobilenativefoundation/store/core5/InsertionStrategy.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.core5
2 |
3 | @ExperimentalStoreApi
4 | enum class InsertionStrategy {
5 | APPEND,
6 | PREPEND,
7 | REPLACE,
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/org/mobilenativefoundation/store/core5/KeyProvider.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.core5
2 |
3 | @ExperimentalStoreApi
4 | interface KeyProvider> {
5 | fun fromCollection(
6 | key: StoreKey.Collection,
7 | value: Single,
8 | ): StoreKey.Single
9 |
10 | fun fromSingle(
11 | key: StoreKey.Single,
12 | value: Single,
13 | ): StoreKey.Collection
14 | }
15 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/org/mobilenativefoundation/store/core5/StoreData.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.core5
2 |
3 | /**
4 | * An interface that defines items that can be uniquely identified.
5 | * Every item that implements the [StoreData] interface must have a means of identification.
6 | * This is useful in scenarios when data can be represented as singles or collections.
7 | */
8 | @ExperimentalStoreApi
9 | interface StoreData {
10 | /**
11 | * Represents a single identifiable item.
12 | */
13 | interface Single : StoreData {
14 | val id: Id
15 | }
16 |
17 | /**
18 | * Represents a collection of identifiable items.
19 | */
20 | interface Collection> : StoreData {
21 | val items: List
22 |
23 | /**
24 | * Returns a new collection with the updated items.
25 | */
26 | fun copyWith(items: List): Collection
27 |
28 | /**
29 | * Inserts items to the existing collection and returns the updated collection.
30 | */
31 | fun insertItems(
32 | strategy: InsertionStrategy,
33 | items: List,
34 | ): Collection
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/org/mobilenativefoundation/store/core5/StoreKey.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.core5
2 |
3 | /**
4 | * An interface that defines keys used by Store for data-fetching operations.
5 | * Allows Store to fetch individual items and collections of items.
6 | * Provides mechanisms for ID-based fetch, page-based fetch, and cursor-based fetch.
7 | * Includes options for sorting and filtering.
8 | */
9 | @ExperimentalStoreApi
10 | interface StoreKey {
11 | /**
12 | * Represents a key for fetching an individual item.
13 | */
14 | interface Single : StoreKey {
15 | val id: Id
16 | }
17 |
18 | /**
19 | * Represents a key for fetching collections of items.
20 | */
21 | interface Collection : StoreKey {
22 | val insertionStrategy: InsertionStrategy
23 |
24 | /**
25 | * Represents a key for page-based fetching.
26 | */
27 | interface Page : Collection {
28 | val page: Int
29 | val size: Int
30 | val sort: Sort?
31 | val filters: List>?
32 | }
33 |
34 | /**
35 | * Represents a key for cursor-based fetching.
36 | */
37 | interface Cursor : Collection {
38 | val cursor: Id?
39 | val size: Int
40 | val sort: Sort?
41 | val filters: List>?
42 | }
43 | }
44 |
45 | /**
46 | * An enum defining sorting options that can be applied during fetching.
47 | */
48 | enum class Sort {
49 | NEWEST,
50 | OLDEST,
51 | ALPHABETICAL,
52 | REVERSE_ALPHABETICAL,
53 | }
54 |
55 | /**
56 | * Defines filters that can be applied during fetching.
57 | */
58 | interface Filter {
59 | operator fun invoke(items: List): List
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # don't use jetifier, all deps are in androidX already android.enableJetifier=true
2 | android.useAndroidX=true
3 | org.gradle.caching=true
4 | org.gradle.configureondemand=true
5 |
6 | # https://github.com/Kotlin/dokka/issues/1405
7 | org.gradle.jvmargs=-XX:MaxMetaspaceSize=2G
8 |
9 | # POM file
10 | GROUP=org.mobilenativefoundation.store
11 | VERSION_NAME=5.1.0-alpha06
12 | POM_PACKAGING=pom
13 | POM_DESCRIPTION = Store5 is a Kotlin Multiplatform network-resilient repository layer
14 |
15 | POM_URL = https://github.com/MobileNativeFoundation/Store
16 | POM_SCM_URL=https://github.com/MobileNativeFoundation/Store
17 | POM_SCM_CONNECTION=scm:git:https://github.com/MobileNativeFoundation/Store.git
18 | POM_SCM_DEV_CONNECTION=scm:git:git@github.com:MobileNativeFoundation/Store.git
19 | POM_LICENCE_NAME=Apache License
20 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0
21 | POM_LICENCE_DIST=repo
22 | POM_DEVELOPER_ID=dropbox
23 | POM_DEVELOPER_NAME=Dropbox
24 | kotlinx.atomicfu.enableJvmIrTransformation=false
25 | kotlinx.atomicfu.enableJsIrTransformation=false
26 | kotlin.js.compiler=ir
27 |
28 | org.jetbrains.compose.experimental.uikit.enabled=true
--------------------------------------------------------------------------------
/gradle/jacoco.gradle:
--------------------------------------------------------------------------------
1 | // Merge of
2 | // https://github.com/mgouline/android-samples/blob/master/jacoco/app/build.gradle
3 | // and https://github.com/pushtorefresh/storio/blob/master/gradle/jacoco-android.gradle
4 |
5 | // Enables code coverage for JVM tests.
6 | apply plugin: "jacoco"
7 |
8 | jacoco {
9 | toolVersion = "0.8.12"
10 | }
11 | // Android Gradle Plugin out of the box supports only code coverage for instrumentation tests.
12 | // Creates a task that will merge coverage for all projects
13 | project.afterEvaluate {
14 | def testTaskName = "test"
15 | def coverageTaskName = "${testTaskName}Coverage"
16 |
17 | // Create coverage task of form 'testFlavorTypeUnitTestCoverage' depending on 'testFlavorTypeUnitTest'
18 | task "${coverageTaskName}"(type: JacocoReport, dependsOn: "$testTaskName") {
19 | group = 'Reporting'
20 | description = "Generate Jacoco coverage reports for the build."
21 |
22 | List fileFilter = ['**/R.class',
23 | '**/R$*.class',
24 | '**/*$ViewInjector*.*',
25 | '**/*$ViewBinder*.*',
26 | '**/BuildConfig.*',
27 | '**/Manifest*.*',
28 | '**/*$Lambda$*.*', // Jacoco can not handle several "$" in class name.
29 | '**/*$inlined$*.*', // Kotlin specific, Jacoco can not handle several "$" in class name.
30 | '**/*Module.*', // Modules for Dagger.
31 | '**/*Dagger*.*', // Dagger auto-generated code.
32 | '**/*MembersInjector*.*', // Dagger auto-generated code.
33 | '**/*_Provide*Factory*.*', // Dagger auto-generated code.
34 | '**/atomicfu', // AtomicFU auto-generated code.
35 | '**/test/**/*.*', // Test code
36 | ]
37 | def kotlinFileTree = fileTree(
38 | dir: "${project.buildDir}/classes",
39 | excludes: fileFilter)
40 | logger.debug("Kotlin classes dirs: " + kotlinFileTree)
41 |
42 | classDirectories.setFrom kotlinFileTree
43 |
44 | def coverageSourceDirs = new File(project.projectDir, "src/main/java")
45 | def coverageData = fileTree(dir: new File(project.buildDir, 'jacoco'), include: '*.exec')
46 |
47 | logger.debug("Coverage source dirs: " + coverageSourceDirs)
48 | logger.debug("Coverage execution data: " + executionData)
49 |
50 | additionalSourceDirs.setFrom files(coverageSourceDirs)
51 | sourceDirectories.setFrom files(coverageSourceDirs)
52 | executionData.setFrom files(coverageData)
53 |
54 | reports {
55 | xml.enabled = true
56 | xml.destination = file("${buildDir}/reports/jacoco/report.xml")
57 | html.enabled = true
58 | }
59 | }
60 |
61 | check.dependsOn "${coverageTaskName}"
62 | }
63 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | androidMinSdk = "24"
3 | androidCompileSdk = "33"
4 | androidGradlePlugin = "7.4.2"
5 | androidTargetSdk = "33"
6 | atomicFu = "0.24.0"
7 | baseKotlin = "2.0.20"
8 | dokkaGradlePlugin = "1.9.20"
9 | ktlintGradle = "12.1.0"
10 | jacocoGradlePlugin = "0.8.12"
11 | mavenPublishPlugin = "0.22.0"
12 | moleculeGradlePlugin = "1.2.1"
13 | pagingCompose = "3.3.0-alpha02"
14 | pagingRuntime = "3.2.1"
15 | spotlessPluginGradle = "6.4.1"
16 | junit = "4.13.2"
17 | kotlinxCoroutines = "1.8.1"
18 | kotlinxSerialization = "1.6.3"
19 | kermit = "2.0.5"
20 | testCore = "1.6.1"
21 | kmmBridge = "0.3.2"
22 | ktlint = "0.39.0"
23 | kover = "0.9.0-RC"
24 | store = "5.1.0-alpha06"
25 | truth = "1.1.3"
26 | turbine = "1.2.0"
27 | binary-compatibility-validator = "0.15.0-Beta.2"
28 |
29 | [libraries]
30 | android-gradle-plugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
31 | androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingCompose" }
32 | androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" }
33 | kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "baseKotlin" }
34 | kotlin-serialization-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "baseKotlin" }
35 | dokka-gradle-plugin = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "dokkaGradlePlugin" }
36 | ktlint-gradle-plugin = { group = "org.jlleitschuh.gradle", name = "ktlint-gradle", version.ref = "ktlintGradle" }
37 | jacoco-gradle-plugin = { group = "org.jacoco", name = "org.jacoco.core", version.ref = "jacocoGradlePlugin" }
38 | maven-publish-plugin = { group = "com.vanniktech", name = "gradle-maven-publish-plugin", version.ref = "mavenPublishPlugin" }
39 | kover-gradle-plugin = {group = "org.jetbrains.kotlinx", name = "kover-gradle-plugin", version.ref = "kover"}
40 |
41 | atomic-fu-gradle-plugin = { group = "org.jetbrains.kotlinx", name = "atomicfu-gradle-plugin", version.ref = "atomicFu" }
42 | kmmBridge-gradle-plugin = { group = "co.touchlab.faktory.kmmbridge", name = "co.touchlab.faktory.kmmbridge.gradle.plugin", version.ref = "kmmBridge" }
43 |
44 | kotlinx-atomic-fu = { group = "org.jetbrains.kotlinx", name = "atomicfu", version.ref = "atomicFu" }
45 | kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "baseKotlin" }
46 | kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinxSerialization" }
47 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
48 | kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
49 | kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
50 | kotlinx-coroutines-rx2 = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-rx2", version.ref = "kotlinxCoroutines" }
51 | kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version = "0.6.2" }
52 | molecule-gradle-plugin = { module = "app.cash.molecule:molecule-gradle-plugin", version.ref = "moleculeGradlePlugin" }
53 | molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "moleculeGradlePlugin" }
54 | rxjava = { group = "io.reactivex.rxjava2", name = "rxjava", version = "2.2.21" }
55 | androidx-test-core = { group = "androidx.test", name = "core", version.ref = "testCore" }
56 | kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
57 | junit = { group = "junit", name = "junit", version.ref = "junit" }
58 | google-truth = { group = "com.google.truth", name = "truth", version.ref = "truth" }
59 | touchlab-kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" }
60 | turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
61 | binary-compatibility-validator = {module = "org.jetbrains.kotlinx:binary-compatibility-validator", version.ref = "binary-compatibility-validator"}
62 |
63 | [plugins]
64 | ktlint = {id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlintGradle"}
65 | binary-compatibility-validator = {id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator"}
66 | kover = {id = "org.jetbrains.kotlinx.kover", version.ref = "kover"}
67 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MobileNativeFoundation/Store/d7af217c08dca0630719d9c99ccfb532035eb2ed/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo. 1>&2
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48 | echo. 1>&2
49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50 | echo location of your Java installation. 1>&2
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo. 1>&2
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62 | echo. 1>&2
63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64 | echo location of your Java installation. 1>&2
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/multicast/api/android/multicast.api:
--------------------------------------------------------------------------------
1 | public final class org/mobilenativefoundation/store/multicast/BuildConfig {
2 | public static final field BUILD_TYPE Ljava/lang/String;
3 | public static final field DEBUG Z
4 | public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
5 | public fun ()V
6 | }
7 |
8 | public final class org/mobilenativefoundation/store/multicast5/Multicaster {
9 | public fun (Lkotlinx/coroutines/CoroutineScope;ILkotlinx/coroutines/flow/Flow;ZZLkotlin/jvm/functions/Function2;)V
10 | public synthetic fun (Lkotlinx/coroutines/CoroutineScope;ILkotlinx/coroutines/flow/Flow;ZZLkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
11 | public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
12 | public final fun newDownstream (Z)Lkotlinx/coroutines/flow/Flow;
13 | public static synthetic fun newDownstream$default (Lorg/mobilenativefoundation/store/multicast5/Multicaster;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/multicast/api/jvm/multicast.api:
--------------------------------------------------------------------------------
1 | public final class org/mobilenativefoundation/store/multicast5/Multicaster {
2 | public fun (Lkotlinx/coroutines/CoroutineScope;ILkotlinx/coroutines/flow/Flow;ZZLkotlin/jvm/functions/Function2;)V
3 | public synthetic fun (Lkotlinx/coroutines/CoroutineScope;ILkotlinx/coroutines/flow/Flow;ZZLkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
4 | public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
5 | public final fun newDownstream (Z)Lkotlinx/coroutines/flow/Flow;
6 | public static synthetic fun newDownstream$default (Lorg/mobilenativefoundation/store/multicast5/Multicaster;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/multicast/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.mobilenativefoundation.store.multiplatform")
3 | }
4 |
5 | kotlin {
6 |
7 | sourceSets {
8 |
9 | val commonMain by getting {
10 | dependencies {
11 | api(libs.kotlinx.atomic.fu)
12 | implementation(libs.kotlinx.coroutines.core)
13 | }
14 | }
15 |
16 | val commonTest by getting {
17 | dependencies {
18 | implementation(libs.junit)
19 | implementation(libs.kotlinx.coroutines.test)
20 | implementation(libs.turbine)
21 | }
22 | }
23 | }
24 | }
25 |
26 | android {
27 | namespace = "org.mobilenativefoundation.store.multicast"
28 | }
29 |
--------------------------------------------------------------------------------
/multicast/config/ktlint/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/multicast/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=org.mobilenativefoundation.store
2 | POM_ARTIFACT_ID=multicast5
3 | POM_PACKAGING=jar
--------------------------------------------------------------------------------
/multicast/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/multicast/src/commonMain/kotlin/org/mobilenativefoundation/store/multicast5/Actor.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.multicast5
2 |
3 | import kotlinx.coroutines.CompletionHandler
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.channels.Channel
6 | import kotlinx.coroutines.channels.ReceiveChannel
7 | import kotlinx.coroutines.channels.SendChannel
8 | import kotlinx.coroutines.isActive
9 | import kotlinx.coroutines.launch
10 | import kotlin.coroutines.CoroutineContext
11 | import kotlin.coroutines.EmptyCoroutineContext
12 |
13 | /*
14 | * Credits to nickallendev
15 | * https://discuss.kotlinlang.org/t/actor-kotlin-common/19569
16 | */
17 | internal fun CoroutineScope.actor(
18 | context: CoroutineContext = EmptyCoroutineContext,
19 | capacity: Int = 0,
20 | onCompletion: CompletionHandler? = null,
21 | block: suspend CoroutineScope.(ReceiveChannel) -> Unit,
22 | ): SendChannel {
23 | val channel = Channel(capacity)
24 | val job =
25 | launch(context) {
26 | try {
27 | block(channel)
28 | } finally {
29 | if (isActive) channel.cancel()
30 | }
31 | }
32 | if (onCompletion != null) job.invokeOnCompletion(handler = onCompletion)
33 | return channel
34 | }
35 |
--------------------------------------------------------------------------------
/multicast/src/commonMain/kotlin/org/mobilenativefoundation/store/multicast5/SharedFlowProducer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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 | package org.mobilenativefoundation.store.multicast5
17 |
18 | import kotlinx.coroutines.CompletableDeferred
19 | import kotlinx.coroutines.CoroutineScope
20 | import kotlinx.coroutines.CoroutineStart
21 | import kotlinx.coroutines.Job
22 | import kotlinx.coroutines.cancelAndJoin
23 | import kotlinx.coroutines.channels.ClosedSendChannelException
24 | import kotlinx.coroutines.flow.Flow
25 | import kotlinx.coroutines.flow.catch
26 | import kotlinx.coroutines.launch
27 |
28 | /**
29 | * A flow collector that works with a [ChannelManager] to collect values from an upstream flow
30 | * and dispatch to the [sendUpsteamMessage] which then dispatches to downstream collectors.
31 | *
32 | * They work in sync such that this producer always expects an ack from the [ChannelManager] after
33 | * sending an event.
34 | *
35 | * Cancellation of the collection might be triggered by both this producer (e.g. upstream completes)
36 | * or the [ChannelManager] (e.g. all active collectors complete).
37 | */
38 | internal class SharedFlowProducer(
39 | private val scope: CoroutineScope,
40 | private val src: Flow,
41 | private val sendUpsteamMessage: suspend (ChannelManager.Message.Dispatch) -> Unit,
42 | ) {
43 | private val collectionJob: Job =
44 | scope.launch(start = CoroutineStart.LAZY) {
45 | try {
46 | src.catch {
47 | sendUpsteamMessage(ChannelManager.Message.Dispatch.Error(it))
48 | }.collect {
49 | val ack = CompletableDeferred()
50 | sendUpsteamMessage(
51 | ChannelManager.Message.Dispatch.Value(
52 | it,
53 | ack,
54 | ),
55 | )
56 | // suspend until at least 1 receives the new value
57 | ack.await()
58 | }
59 | } catch (closed: ClosedSendChannelException) {
60 | // ignore. if consumers are gone, it might close itself.
61 | }
62 | }
63 |
64 | /**
65 | * Starts the collection of the upstream flow.
66 | */
67 | fun start() {
68 | scope.launch {
69 | try {
70 | // trigger start of the collection and wait until collection ends, either due to an
71 | // error or ordered by the channel manager
72 | collectionJob.join()
73 | } finally {
74 | // cleanup the channel manager so that downstreams can be closed if they are not
75 | // closed already and leftovers can be moved to a new producer if necessary.
76 | try {
77 | sendUpsteamMessage(ChannelManager.Message.Dispatch.UpstreamFinished(this@SharedFlowProducer))
78 | } catch (closed: ClosedSendChannelException) {
79 | // it might close before us, its fine.
80 | }
81 | }
82 | }
83 | }
84 |
85 | suspend fun cancelAndJoin() {
86 | collectionJob.cancelAndJoin()
87 | }
88 |
89 | fun cancel() {
90 | collectionJob.cancel()
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/multicast/src/commonMain/kotlin/org/mobilenativefoundation/store/multicast5/StoreRealActor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 Google LLC
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 | * https://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 | package org.mobilenativefoundation.store.multicast5
17 |
18 | import kotlinx.atomicfu.atomic
19 | import kotlinx.coroutines.CompletableDeferred
20 | import kotlinx.coroutines.CoroutineScope
21 | import kotlinx.coroutines.channels.ClosedSendChannelException
22 | import kotlinx.coroutines.channels.SendChannel
23 |
24 | /**
25 | * Simple actor implementation abstracting away Coroutine.actor since it is deprecated.
26 | * It also enforces a 0 capacity buffer.
27 | */
28 | @Suppress("EXPERIMENTAL_API_USAGE")
29 | internal abstract class StoreRealActor(
30 | scope: CoroutineScope,
31 | ) {
32 | private val inboundChannel: SendChannel
33 | private val closeCompleted = CompletableDeferred()
34 | private val didClose = atomic(false)
35 |
36 | init {
37 | inboundChannel =
38 | scope.actor(
39 | capacity = 0,
40 | ) {
41 | try {
42 | for (msg in it) {
43 | if (msg === CLOSE_TOKEN) {
44 | doClose()
45 | break
46 | } else {
47 | @Suppress("UNCHECKED_CAST")
48 | handle(msg as T)
49 | }
50 | }
51 | } finally {
52 | doClose()
53 | }
54 | }
55 | }
56 |
57 | private fun doClose() {
58 | if (didClose.compareAndSet(expect = false, update = true)) {
59 | try {
60 | onClosed()
61 | } finally {
62 | inboundChannel.close()
63 | closeCompleted.complete(Unit)
64 | }
65 | }
66 | }
67 |
68 | open fun onClosed() = Unit
69 |
70 | abstract suspend fun handle(msg: T)
71 |
72 | suspend fun send(msg: T) {
73 | inboundChannel.send(msg)
74 | }
75 |
76 | suspend fun close() {
77 | try {
78 | // using a custom token to close so that we can gracefully close the downstream
79 | inboundChannel.send(CLOSE_TOKEN)
80 | // wait until close is done done
81 | closeCompleted.await()
82 | } catch (closed: ClosedSendChannelException) {
83 | // already closed, ignore
84 | }
85 | }
86 |
87 | companion object {
88 | val CLOSE_TOKEN = Any()
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/multicast/src/commonTest/kotlin/org/mobilenativefoundation/store/multicast5/StoreChannelManagerTests.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.multicast5
2 |
3 | import app.cash.turbine.test
4 | import kotlinx.coroutines.CoroutineScope
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.channels.Channel
7 | import kotlinx.coroutines.flow.consumeAsFlow
8 | import kotlinx.coroutines.flow.filterIsInstance
9 | import kotlinx.coroutines.flow.flow
10 | import kotlinx.coroutines.flow.onEach
11 | import kotlinx.coroutines.launch
12 | import kotlinx.coroutines.sync.Mutex
13 | import kotlinx.coroutines.sync.withLock
14 | import kotlinx.coroutines.test.runTest
15 | import kotlin.test.Test
16 | import kotlin.test.assertEquals
17 |
18 | class StoreChannelManagerTests {
19 | @Test
20 | fun cancelledDownstreamChannelShouldNotCancelOtherChannels() =
21 | runTest {
22 | val coroutineScope = CoroutineScope(Dispatchers.Default)
23 | val lockUpstream = Mutex(true)
24 | val testMessages = listOf(1, 2, 3)
25 | val numChannels = 20
26 | val upstreamFlow =
27 | flow {
28 | lockUpstream.withLock {
29 | testMessages.onEach { emit(it) }
30 | }
31 | }
32 | val channelManager =
33 | StoreChannelManager(
34 | scope = coroutineScope,
35 | bufferSize = 0,
36 | upstream = upstreamFlow,
37 | piggybackingDownstream = false,
38 | keepUpstreamAlive = false,
39 | onEach = { },
40 | )
41 | val channels = createChannels(numChannels)
42 | val channelToBeCancelled =
43 | Channel>(Channel.UNLIMITED)
44 | .also { channel ->
45 | coroutineScope.launch {
46 | channel.consumeAsFlow().test {
47 | cancelAndIgnoreRemainingEvents()
48 | }
49 | }
50 | }
51 | coroutineScope.launch {
52 | channels.forEach { channelManager.addDownstream(it) }
53 | lockUpstream.unlock()
54 | }
55 | coroutineScope.launch {
56 | channelManager.addDownstream(channelToBeCancelled)
57 | }
58 |
59 | channels.forEach { channel ->
60 | val messagesFlow =
61 | channel.consumeAsFlow()
62 | .filterIsInstance>()
63 | .onEach { it.delivered.complete(Unit) }
64 |
65 | messagesFlow.test {
66 | for (message in testMessages) {
67 | val dispatchValue = awaitItem()
68 | assertEquals(message, dispatchValue.value)
69 | }
70 | awaitComplete()
71 | }
72 | }
73 | }
74 |
75 | private fun createChannels(count: Int): List>> {
76 | return (1..count).map { Channel(Channel.UNLIMITED) }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/pull_request_template.md:
--------------------------------------------------------------------------------
1 | Closes # (Issue number)
2 |
3 | ## Description
4 | What changes are you introducing with this PR? Please include motivation and context.
5 |
6 | ## Type of Change
7 | - [ ] Bug fix (non-breaking change which fixes an issue)
8 | - [ ] New feature (non-breaking change which adds functionality)
9 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
10 | - [ ] This change requires a documentation update
11 |
12 | ## Test Plan
13 | How has this been tested? Please describe the tests that you added to verify your changes.
14 |
15 | ## Checklist:
16 | Before submitting your PR, please review and check all of the following:
17 | - [ ] I have performed a self-review of my own code
18 | - [ ] I have commented my code, particularly in hard-to-understand areas
19 | - [ ] I have made corresponding changes to the documentation
20 | - [ ] My changes generate no new warnings
21 | - [ ] I have added tests that prove my change is effective
22 | - [ ] New and existing unit tests pass locally with my changes
23 |
24 | ## Additional Notes:
25 | Add any other information about the PR here.
26 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": [
4 | "config:recommended"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/rx2/api/rx2.api:
--------------------------------------------------------------------------------
1 | public final class org/mobilenativefoundation/store/rx2/BuildConfig {
2 | public static final field BUILD_TYPE Ljava/lang/String;
3 | public static final field DEBUG Z
4 | public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
5 | public fun ()V
6 | }
7 |
8 | public final class org/mobilenativefoundation/store/rx2/RxFetcherKt {
9 | public static final fun ofFlowable (Lorg/mobilenativefoundation/store/store5/Fetcher$Companion;Lkotlin/jvm/functions/Function1;)Lorg/mobilenativefoundation/store/store5/Fetcher;
10 | public static final fun ofResultFlowable (Lorg/mobilenativefoundation/store/store5/Fetcher$Companion;Lkotlin/jvm/functions/Function1;)Lorg/mobilenativefoundation/store/store5/Fetcher;
11 | public static final fun ofResultSingle (Lorg/mobilenativefoundation/store/store5/Fetcher$Companion;Lkotlin/jvm/functions/Function1;)Lorg/mobilenativefoundation/store/store5/Fetcher;
12 | public static final fun ofSingle (Lorg/mobilenativefoundation/store/store5/Fetcher$Companion;Lkotlin/jvm/functions/Function1;)Lorg/mobilenativefoundation/store/store5/Fetcher;
13 | }
14 |
15 | public final class org/mobilenativefoundation/store/rx2/RxSourceOfTruthKt {
16 | public static final fun ofFlowable (Lorg/mobilenativefoundation/store/store5/SourceOfTruth$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/store5/SourceOfTruth;
17 | public static synthetic fun ofFlowable$default (Lorg/mobilenativefoundation/store/store5/SourceOfTruth$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lorg/mobilenativefoundation/store/store5/SourceOfTruth;
18 | public static final fun ofMaybe (Lorg/mobilenativefoundation/store/store5/SourceOfTruth$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;)Lorg/mobilenativefoundation/store/store5/SourceOfTruth;
19 | public static synthetic fun ofMaybe$default (Lorg/mobilenativefoundation/store/store5/SourceOfTruth$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lorg/mobilenativefoundation/store/store5/SourceOfTruth;
20 | }
21 |
22 | public final class org/mobilenativefoundation/store/rx2/RxStoreBuilderKt {
23 | public static final fun withScheduler (Lorg/mobilenativefoundation/store/store5/StoreBuilder;Lio/reactivex/Scheduler;)Lorg/mobilenativefoundation/store/store5/StoreBuilder;
24 | }
25 |
26 | public final class org/mobilenativefoundation/store/rx2/RxStoreKt {
27 | public static final fun freshSingle (Lorg/mobilenativefoundation/store/store5/Store;Ljava/lang/Object;)Lio/reactivex/Single;
28 | public static final fun getSingle (Lorg/mobilenativefoundation/store/store5/Store;Ljava/lang/Object;)Lio/reactivex/Single;
29 | public static final fun observe (Lorg/mobilenativefoundation/store/store5/Store;Lorg/mobilenativefoundation/store/store5/StoreReadRequest;)Lio/reactivex/Flowable;
30 | public static final fun observeClear (Lorg/mobilenativefoundation/store/store5/Store;Ljava/lang/Object;)Lio/reactivex/Completable;
31 | public static final fun observeClearAll (Lorg/mobilenativefoundation/store/store5/Store;)Lio/reactivex/Completable;
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/rx2/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:Suppress("UnstableApiUsage")
2 |
3 | plugins {
4 | id("org.mobilenativefoundation.store.android")
5 | }
6 |
7 | dependencies {
8 | implementation(libs.kotlinx.coroutines.rx2)
9 | implementation(libs.kotlinx.coroutines.core)
10 | implementation(libs.kotlinx.coroutines.android)
11 | implementation(libs.rxjava)
12 | implementation(projects.store)
13 |
14 | testImplementation(kotlin("test"))
15 | testImplementation(libs.junit)
16 | testImplementation(libs.google.truth)
17 | testImplementation(libs.androidx.test.core)
18 | testImplementation(libs.kotlinx.coroutines.test)
19 | }
20 |
21 | android {
22 | namespace = "org.mobilenativefoundation.store.rx2"
23 | }
24 |
--------------------------------------------------------------------------------
/rx2/config/ktlint/baseline.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/rx2/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=org.mobilenativefoundation.store
2 | POM_ARTIFACT_ID=rx2
3 | POM_PACKAGING=jar
--------------------------------------------------------------------------------
/rx2/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/rx2/src/main/kotlin/org/mobilenativefoundation/store/rx2/RxFetcher.kt:
--------------------------------------------------------------------------------
1 | package org.mobilenativefoundation.store.rx2
2 |
3 | import io.reactivex.Flowable
4 | import io.reactivex.Single
5 | import kotlinx.coroutines.reactive.asFlow
6 | import org.mobilenativefoundation.store.store5.Fetcher
7 | import org.mobilenativefoundation.store.store5.FetcherResult
8 | import org.mobilenativefoundation.store.store5.Store
9 |
10 | /**
11 | * Creates a new [Fetcher] from a [flowableFactory].
12 | *
13 | * [Store] does not catch exception thrown in [flowableFactory] or in the returned [Flowable]. These
14 | * exception will be propagated to the caller.
15 | *
16 | * Use when creating a [Store] that fetches objects in a multiple responses per request
17 | * network protocol (e.g Web Sockets).
18 | *
19 | * @param flowableFactory a factory for a [Flowable] source of network records.
20 | */
21 | fun Fetcher.Companion.ofResultFlowable(
22 | flowableFactory: (key: Key) -> Flowable>,
23 | ): Fetcher = ofResultFlow { key: Key -> flowableFactory(key).asFlow() }
24 |
25 | /**
26 | * "Creates" a [Fetcher] from a [singleFactory].
27 | *
28 | * [Store] does not catch exception thrown in [singleFactory] or in the returned [Single]. These
29 | * exception will be propagated to the caller.
30 | *
31 | * Use when creating a [Store] that fetches objects in a single response per request network
32 | * protocol (e.g Http).
33 | *
34 | * @param singleFactory a factory for a [Single] source of network records.
35 | */
36 | fun Fetcher.Companion.ofResultSingle(
37 | singleFactory: (key: Key) -> Single>,
38 | ): Fetcher = ofResultFlowable { key: Key -> singleFactory(key).toFlowable() }
39 |
40 | /**
41 | * "Creates" a [Fetcher] from a [flowableFactory] and translate the results to a [FetcherResult].
42 | *
43 | * Emitted values will be wrapped in [FetcherResult.Data]. if an exception disrupts the stream then
44 | * it will be wrapped in [FetcherResult.Error]. Exceptions thrown in [flowableFactory] itself are
45 | * not caught and will be returned to the caller.
46 | *
47 | * Use when creating a [Store] that fetches objects in a multiple responses per request
48 | * network protocol (e.g Web Sockets).
49 | *
50 | * @param flowFactory a factory for a [Flowable] source of network records.
51 | */
52 | fun Fetcher.Companion.ofFlowable(flowableFactory: (key: Key) -> Flowable