Skip to content

Commit

Permalink
Separate MutableStoreBuilder from StoreBuilder (#542)
Browse files Browse the repository at this point in the history
* Separate MutableStoreBuilder from StoreBuilder

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Enable conversion from StoreBuilder to MutableStoreBuilder

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Clean up

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

* Fix tests

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>

---------

Signed-off-by: Matt Ramotar <mramotar@dropbox.com>
  • Loading branch information
matt-ramotar authored Apr 13, 2023
1 parent fc249e1 commit e050a15
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.mobilenativefoundation.store.store5

import kotlinx.coroutines.CoroutineScope
import org.mobilenativefoundation.store.store5.impl.mutableStoreBuilderFromFetcherAndSourceOfTruth

interface MutableStoreBuilder<Key : Any, Network : Any, Output : Any, Local : Any> {

fun <Response : Any> build(
updater: Updater<Key, Output, Response>,
bookkeeper: Bookkeeper<Key>? = null
): MutableStore<Key, Output>

/**
* A store multicasts same [Output] value to many consumers (Similar to RxJava.share()), by default
* [Store] will open a global scope for management of shared responses, if instead you'd like to control
* the scope that sharing/multicasting happens in you can pass a @param [scope]
*
* @param scope - scope to use for sharing
*/
fun scope(scope: CoroutineScope): MutableStoreBuilder<Key, Network, Output, Local>

/**
* controls eviction policy for a store cache, use [MemoryPolicy.MemoryPolicyBuilder] to configure a TTL
* or size based eviction
* Example: MemoryPolicy.builder().setExpireAfterWrite(10.seconds).build()
*/
fun cachePolicy(memoryPolicy: MemoryPolicy<Key, Output>?): MutableStoreBuilder<Key, Network, Output, Local>

/**
* by default a Store caches in memory with a default policy of max items = 100
*/
fun disableCache(): MutableStoreBuilder<Key, Network, Output, Local>

fun converter(converter: Converter<Network, Output, Local>):
MutableStoreBuilder<Key, Network, Output, Local>

fun validator(validator: Validator<Output>): MutableStoreBuilder<Key, Network, Output, Local>

companion object {
/**
* Creates a new [MutableStoreBuilder] from a [Fetcher] and a [SourceOfTruth].
*
* @param fetcher a function for fetching a flow of network records.
* @param sourceOfTruth a [SourceOfTruth] for the store.
*/
fun <Key : Any, Network : Any, Output : Any, Local : Any> from(
fetcher: Fetcher<Key, Network>,
sourceOfTruth: SourceOfTruth<Key, Local>
): MutableStoreBuilder<Key, Network, Output, Local> =
mutableStoreBuilderFromFetcherAndSourceOfTruth(fetcher = fetcher, sourceOfTruth = sourceOfTruth)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,10 @@ import org.mobilenativefoundation.store.store5.impl.storeBuilderFromFetcherAndSo
/**
* Main entry point for creating a [Store].
*/
interface StoreBuilder<Key : Any, Network : Any, Output : Any, Local : Any> {
interface StoreBuilder<Key : Any, Output : Any> {
fun build(): Store<Key, Output>

fun <Response : Any> build(
updater: Updater<Key, Output, Response>,
bookkeeper: Bookkeeper<Key>? = null
): MutableStore<Key, Output>
fun <Network : Any, Local : Any> toMutableStoreBuilder(): MutableStoreBuilder<Key, Network, Output, Local>

/**
* A store multicasts same [Output] value to many consumers (Similar to RxJava.share()), by default
Expand All @@ -37,46 +34,41 @@ interface StoreBuilder<Key : Any, Network : Any, Output : Any, Local : Any> {
*
* @param scope - scope to use for sharing
*/
fun scope(scope: CoroutineScope): StoreBuilder<Key, Network, Output, Local>
fun scope(scope: CoroutineScope): StoreBuilder<Key, Output>

/**
* controls eviction policy for a store cache, use [MemoryPolicy.MemoryPolicyBuilder] to configure a TTL
* or size based eviction
* Example: MemoryPolicy.builder().setExpireAfterWrite(10.seconds).build()
*/
fun cachePolicy(memoryPolicy: MemoryPolicy<Key, Output>?): StoreBuilder<Key, Network, Output, Local>
fun cachePolicy(memoryPolicy: MemoryPolicy<Key, Output>?): StoreBuilder<Key, Output>

/**
* by default a Store caches in memory with a default policy of max items = 100
*/
fun disableCache(): StoreBuilder<Key, Network, Output, Local>
fun disableCache(): StoreBuilder<Key, Output>

fun converter(converter: Converter<Network, Output, Local>):
StoreBuilder<Key, Network, Output, Local>

fun validator(validator: Validator<Output>): StoreBuilder<Key, Network, Output, Local>
fun validator(validator: Validator<Output>): StoreBuilder<Key, Output>

companion object {

/**
* Creates a new [StoreBuilder] from a [Fetcher].
*
* @param fetcher a [Fetcher] flow of network records.
*/
fun <Key : Any, Network : Any, Output : Any> from(
fetcher: Fetcher<Key, Network>,
): StoreBuilder<Key, Network, Output, *> = storeBuilderFromFetcher(fetcher = fetcher)
fun <Key : Any, Input : Any, Output : Any> from(
fetcher: Fetcher<Key, Input>,
): StoreBuilder<Key, Output> = storeBuilderFromFetcher(fetcher = fetcher)

/**
* Creates a new [StoreBuilder] from a [Fetcher] and a [SourceOfTruth].
*
* @param fetcher a function for fetching a flow of network records.
* @param sourceOfTruth a [SourceOfTruth] for the store.
*/
fun <Key : Any, Network : Any, Output : Any, Local : Any> from(
fetcher: Fetcher<Key, Network>,
sourceOfTruth: SourceOfTruth<Key, Local>
): StoreBuilder<Key, Network, Output, Local> =
storeBuilderFromFetcherAndSourceOfTruth(fetcher = fetcher, sourceOfTruth = sourceOfTruth)
fun <Key : Any, Input : Any, Output : Any> from(
fetcher: Fetcher<Key, Input>,
sourceOfTruth: SourceOfTruth<Key, Input>
): StoreBuilder<Key, Output> = storeBuilderFromFetcherAndSourceOfTruth(fetcher = fetcher, sourceOfTruth = sourceOfTruth)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.mobilenativefoundation.store.store5.impl

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import org.mobilenativefoundation.store.store5.Bookkeeper
import org.mobilenativefoundation.store.store5.Converter
import org.mobilenativefoundation.store.store5.Fetcher
import org.mobilenativefoundation.store.store5.MemoryPolicy
import org.mobilenativefoundation.store.store5.MutableStore
import org.mobilenativefoundation.store.store5.MutableStoreBuilder
import org.mobilenativefoundation.store.store5.SourceOfTruth
import org.mobilenativefoundation.store.store5.Store
import org.mobilenativefoundation.store.store5.StoreDefaults
import org.mobilenativefoundation.store.store5.Updater
import org.mobilenativefoundation.store.store5.Validator
import org.mobilenativefoundation.store.store5.impl.extensions.asMutableStore

fun <Key : Any, Network : Any, Output : Any, Local : Any> mutableStoreBuilderFromFetcher(
fetcher: Fetcher<Key, Network>,
): MutableStoreBuilder<Key, Network, Output, Local> = RealMutableStoreBuilder(fetcher)

fun <Key : Any, Network : Any, Output : Any, Local : Any> mutableStoreBuilderFromFetcherAndSourceOfTruth(
fetcher: Fetcher<Key, Network>,
sourceOfTruth: SourceOfTruth<Key, Local>,
): MutableStoreBuilder<Key, Network, Output, Local> = RealMutableStoreBuilder(fetcher, sourceOfTruth)

internal class RealMutableStoreBuilder<Key : Any, Network : Any, Output : Any, Local : Any>(
private val fetcher: Fetcher<Key, Network>,
private val sourceOfTruth: SourceOfTruth<Key, Local>? = null,
) : MutableStoreBuilder<Key, Network, Output, Local> {
private var scope: CoroutineScope? = null
private var cachePolicy: MemoryPolicy<Key, Output>? = StoreDefaults.memoryPolicy
private var converter: Converter<Network, Output, Local>? = null
private var validator: Validator<Output>? = null

override fun scope(scope: CoroutineScope): MutableStoreBuilder<Key, Network, Output, Local> {
this.scope = scope
return this
}

override fun cachePolicy(memoryPolicy: MemoryPolicy<Key, Output>?): MutableStoreBuilder<Key, Network, Output, Local> {
cachePolicy = memoryPolicy
return this
}

override fun disableCache(): MutableStoreBuilder<Key, Network, Output, Local> {
cachePolicy = null
return this
}

override fun validator(validator: Validator<Output>): MutableStoreBuilder<Key, Network, Output, Local> {
this.validator = validator
return this
}

override fun converter(converter: Converter<Network, Output, Local>): MutableStoreBuilder<Key, Network, Output, Local> {
this.converter = converter
return this
}

fun build(): Store<Key, Output> = RealStore(
scope = scope ?: GlobalScope,
sourceOfTruth = sourceOfTruth,
fetcher = fetcher,
memoryPolicy = cachePolicy,
converter = converter,
validator = validator
)

override fun <UpdaterResult : Any> build(
updater: Updater<Key, Output, UpdaterResult>,
bookkeeper: Bookkeeper<Key>?
): MutableStore<Key, Output> =
build().asMutableStore<Key, Network, Output, Local, UpdaterResult>(
updater = updater,
bookkeeper = bookkeeper
)
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,58 @@
@file:Suppress("UNCHECKED_CAST")

package org.mobilenativefoundation.store.store5.impl

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import org.mobilenativefoundation.store.store5.Bookkeeper
import org.mobilenativefoundation.store.store5.Converter
import org.mobilenativefoundation.store.store5.Fetcher
import org.mobilenativefoundation.store.store5.MemoryPolicy
import org.mobilenativefoundation.store.store5.MutableStore
import org.mobilenativefoundation.store.store5.MutableStoreBuilder
import org.mobilenativefoundation.store.store5.SourceOfTruth
import org.mobilenativefoundation.store.store5.Store
import org.mobilenativefoundation.store.store5.StoreBuilder
import org.mobilenativefoundation.store.store5.StoreDefaults
import org.mobilenativefoundation.store.store5.Updater
import org.mobilenativefoundation.store.store5.Validator
import org.mobilenativefoundation.store.store5.impl.extensions.asMutableStore

fun <Key : Any, Network : Any, Output : Any> storeBuilderFromFetcher(
fetcher: Fetcher<Key, Network>,
fun <Key : Any, Input : Any, Output : Any> storeBuilderFromFetcher(
fetcher: Fetcher<Key, Input>,
sourceOfTruth: SourceOfTruth<Key, *>? = null,
): StoreBuilder<Key, Network, Output, *> = RealStoreBuilder(fetcher, sourceOfTruth)
): StoreBuilder<Key, Output> = RealStoreBuilder(fetcher, sourceOfTruth)

fun <Key : Any, Output : Any, Network : Any, Local : Any> storeBuilderFromFetcherAndSourceOfTruth(
fetcher: Fetcher<Key, Network>,
sourceOfTruth: SourceOfTruth<Key, Local>,
): StoreBuilder<Key, Network, Output, Local> = RealStoreBuilder(fetcher, sourceOfTruth)
fun <Key : Any, Input : Any, Output : Any> storeBuilderFromFetcherAndSourceOfTruth(
fetcher: Fetcher<Key, Input>,
sourceOfTruth: SourceOfTruth<Key, *>,
): StoreBuilder<Key, Output> = RealStoreBuilder(fetcher, sourceOfTruth)

internal class RealStoreBuilder<Key : Any, Network : Any, Output : Any, Local : Any>(
private val fetcher: Fetcher<Key, Network>,
private val sourceOfTruth: SourceOfTruth<Key, Local>? = null
) : StoreBuilder<Key, Network, Output, Local> {
) : StoreBuilder<Key, Output> {
private var scope: CoroutineScope? = null
private var cachePolicy: MemoryPolicy<Key, Output>? = StoreDefaults.memoryPolicy
private var converter: Converter<Network, Output, Local>? = null
private var validator: Validator<Output>? = null

override fun scope(scope: CoroutineScope): StoreBuilder<Key, Network, Output, Local> {
override fun scope(scope: CoroutineScope): StoreBuilder<Key, Output> {
this.scope = scope
return this
}

override fun cachePolicy(memoryPolicy: MemoryPolicy<Key, Output>?): StoreBuilder<Key, Network, Output, Local> {
override fun cachePolicy(memoryPolicy: MemoryPolicy<Key, Output>?): StoreBuilder<Key, Output> {
cachePolicy = memoryPolicy
return this
}

override fun disableCache(): StoreBuilder<Key, Network, Output, Local> {
override fun disableCache(): StoreBuilder<Key, Output> {
cachePolicy = null
return this
}

override fun validator(validator: Validator<Output>): StoreBuilder<Key, Network, Output, Local> {
override fun validator(validator: Validator<Output>): StoreBuilder<Key, Output> {
this.validator = validator
return this
}

override fun converter(converter: Converter<Network, Output, Local>): StoreBuilder<Key, Network, Output, Local> {
this.converter = converter
return this
}

override fun build(): Store<Key, Output> = RealStore(
scope = scope ?: GlobalScope,
sourceOfTruth = sourceOfTruth,
Expand All @@ -68,12 +62,24 @@ internal class RealStoreBuilder<Key : Any, Network : Any, Output : Any, Local :
validator = validator
)

override fun <UpdaterResult : Any> build(
updater: Updater<Key, Output, UpdaterResult>,
bookkeeper: Bookkeeper<Key>?
): MutableStore<Key, Output> =
build().asMutableStore<Key, Network, Output, Local, UpdaterResult>(
updater = updater,
bookkeeper = bookkeeper
)
override fun <Network : Any, Local : Any> toMutableStoreBuilder(): MutableStoreBuilder<Key, Network, Output, Local> {
fetcher as Fetcher<Key, Network>
return if (sourceOfTruth == null) {
mutableStoreBuilderFromFetcher(fetcher)
} else {
mutableStoreBuilderFromFetcherAndSourceOfTruth<Key, Network, Output, Local>(fetcher, sourceOfTruth as SourceOfTruth<Key, Local>)
}.apply {
if (this@RealStoreBuilder.scope != null) {
scope(this@RealStoreBuilder.scope!!)
}

if (this@RealStoreBuilder.cachePolicy != null) {
cachePolicy(this@RealStoreBuilder.cachePolicy)
}

if (this@RealStoreBuilder.validator != null) {
validator(this@RealStoreBuilder.validator!!)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class ClearAllStoreTests {

@Test
fun callingClearAllOnStoreWithPersisterAndNoInMemoryCacheDeletesAllEntriesFromThePersister() = testScope.runTest {
val store = StoreBuilder.from<String, Int, Int, Int>(
val store = StoreBuilder.from<String, Int, Int>(
fetcher = fetcher,
sourceOfTruth = persister.asSourceOfTruth()
).scope(testScope)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class ClearStoreByKeyTests {
fun callingClearWithKeyOnStoreWithPersisterWithNoInMemoryCacheDeletesTheEntryAssociatedWithTheKeyFromThePersister() = testScope.runTest {
val key = "key"
val value = 1
val store = StoreBuilder.from<String, Int, Int, Int>(
val store = StoreBuilder.from<String, Int, Int>(
fetcher = Fetcher.of { value },
sourceOfTruth = persister.asSourceOfTruth()
).scope(testScope)
Expand Down Expand Up @@ -107,7 +107,7 @@ class ClearStoreByKeyTests {
val key2 = "key2"
val value1 = 1
val value2 = 2
val store = StoreBuilder.from<String, Int, Int, Int>(
val store = StoreBuilder.from<String, Int, Int>(
fetcher = Fetcher.of { key ->
when (key) {
key1 -> value1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,6 @@ class FetcherResponseTests {
)
}

private fun <Key : Any, Network : Any, Output : Any, Local : Any> StoreBuilder<Key, Network, Output, Local>.buildWithTestScope() =
private fun <Key : Any, Output : Any> StoreBuilder<Key, Output>.buildWithTestScope() =
scope(testScope).build()
}
Loading

0 comments on commit e050a15

Please sign in to comment.