diff --git a/soil-query-compose/src/commonMain/kotlin/soil/query/compose/InfiniteQueryComposable.kt b/soil-query-compose/src/commonMain/kotlin/soil/query/compose/InfiniteQueryComposable.kt index a7bef2a..73e2f7c 100644 --- a/soil-query-compose/src/commonMain/kotlin/soil/query/compose/InfiniteQueryComposable.kt +++ b/soil-query-compose/src/commonMain/kotlin/soil/query/compose/InfiniteQueryComposable.kt @@ -15,6 +15,9 @@ import soil.query.QueryChunks import soil.query.QueryClient import soil.query.QueryState import soil.query.QueryStatus +import soil.query.invalidate +import soil.query.loadMore +import soil.query.resume /** * Remember a [InfiniteQueryObject] and subscribes to the query state of [key]. @@ -34,7 +37,7 @@ fun rememberInfiniteQuery( val query = remember(key) { client.getInfiniteQuery(key).also { it.launchIn(scope) } } val state by query.state.collectAsState() LaunchedEffect(query) { - query.start() + query.resume() } return remember(query, state) { state.toInfiniteObject(query = query, select = { it }) @@ -61,7 +64,7 @@ fun rememberInfiniteQuery( val query = remember(key) { client.getInfiniteQuery(key).also { it.launchIn(scope) } } val state by query.state.collectAsState() LaunchedEffect(query) { - query.start() + query.resume() } return remember(query, state) { state.toInfiniteObject(query = query, select = select) diff --git a/soil-query-compose/src/commonMain/kotlin/soil/query/compose/MutationComposable.kt b/soil-query-compose/src/commonMain/kotlin/soil/query/compose/MutationComposable.kt index dff0c52..b986d04 100644 --- a/soil-query-compose/src/commonMain/kotlin/soil/query/compose/MutationComposable.kt +++ b/soil-query-compose/src/commonMain/kotlin/soil/query/compose/MutationComposable.kt @@ -13,6 +13,9 @@ import soil.query.MutationKey import soil.query.MutationRef import soil.query.MutationState import soil.query.MutationStatus +import soil.query.mutate +import soil.query.mutateAsync +import soil.query.reset /** * Remember a [MutationObject] and subscribes to the mutation state of [key]. diff --git a/soil-query-compose/src/commonMain/kotlin/soil/query/compose/QueryComposable.kt b/soil-query-compose/src/commonMain/kotlin/soil/query/compose/QueryComposable.kt index af235ad..345c9c3 100644 --- a/soil-query-compose/src/commonMain/kotlin/soil/query/compose/QueryComposable.kt +++ b/soil-query-compose/src/commonMain/kotlin/soil/query/compose/QueryComposable.kt @@ -14,6 +14,8 @@ import soil.query.QueryKey import soil.query.QueryRef import soil.query.QueryState import soil.query.QueryStatus +import soil.query.invalidate +import soil.query.resume /** * Remember a [QueryObject] and subscribes to the query state of [key]. @@ -32,7 +34,7 @@ fun rememberQuery( val query = remember(key) { client.getQuery(key).also { it.launchIn(scope) } } val state by query.state.collectAsState() LaunchedEffect(query) { - query.start() + query.resume() } return remember(query, state) { state.toObject(query = query, select = { it }) @@ -59,7 +61,7 @@ fun rememberQuery( val query = remember(key) { client.getQuery(key).also { it.launchIn(scope) } } val state by query.state.collectAsState() LaunchedEffect(query) { - query.start() + query.resume() } return remember(query, state) { state.toObject(query = query, select = select) diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/InfiniteQueryRef.kt b/soil-query-core/src/commonMain/kotlin/soil/query/InfiniteQueryRef.kt index 14845b2..bc5467b 100644 --- a/soil-query-core/src/commonMain/kotlin/soil/query/InfiniteQueryRef.kt +++ b/soil-query-core/src/commonMain/kotlin/soil/query/InfiniteQueryRef.kt @@ -3,79 +3,47 @@ package soil.query -import kotlinx.coroutines.CompletableDeferred -import soil.query.core.toResultCallback -import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.flow.StateFlow +import soil.query.core.Actor /** - * A reference to an [Query] for [InfiniteQueryKey]. + * A reference to an Query for [InfiniteQueryKey]. * * @param T Type of data to retrieve. * @param S Type of parameter. - * @property key Instance of a class implementing [InfiniteQueryKey]. - * @param query Transparently referenced [Query]. - * @constructor Creates an [InfiniteQueryRef]. */ -class InfiniteQueryRef( - val key: InfiniteQueryKey, - val options: QueryOptions, - query: Query> -) : Query> by query { +interface InfiniteQueryRef : Actor { - /** - * Starts the [Query]. - * - * This function must be invoked when a new mount point (subscriber) is added. - */ - suspend fun start() { - command.send(InfiniteQueryCommands.Connect(key)) - event.collect(::handleEvent) - } - - /** - * Prefetches the [Query]. - */ - suspend fun prefetch(): Boolean { - val deferred = CompletableDeferred>() - command.send(InfiniteQueryCommands.Connect(key, state.value.revision, deferred.toResultCallback())) - return try { - deferred.await() - true - } catch (e: CancellationException) { - throw e - } catch (e: Throwable) { - false - } - } + val key: InfiniteQueryKey + val options: QueryOptions + val state: StateFlow>> /** - * Invalidates the [Query]. - * - * Calling this function will invalidate the retrieved data of the [Query], - * setting [QueryModel.isInvalidated] to `true` until revalidation is completed. + * Sends a [QueryCommand] to the Actor. */ - suspend fun invalidate() { - command.send(InfiniteQueryCommands.Invalidate(key, state.value.revision)) - } + suspend fun send(command: QueryCommand>) +} - /** - * Resumes the [Query]. - */ - private suspend fun resume() { - command.send(InfiniteQueryCommands.Connect(key, state.value.revision)) - } +/** + * Invalidates the Query. + * + * Calling this function will invalidate the retrieved data of the Query, + * setting [QueryModel.isInvalidated] to `true` until revalidation is completed. + */ +suspend fun InfiniteQueryRef.invalidate() { + send(InfiniteQueryCommands.Invalidate(key, state.value.revision)) +} - /** - * Fetches data for the [InfiniteQueryKey] using the value of [param]. - */ - suspend fun loadMore(param: S) { - command.send(InfiniteQueryCommands.LoadMore(key, param)) - } +/** + * Resumes the Query. + */ +suspend fun InfiniteQueryRef.resume() { + send(InfiniteQueryCommands.Connect(key, state.value.revision)) +} - private suspend fun handleEvent(e: QueryEvent) { - when (e) { - QueryEvent.Invalidate -> invalidate() - QueryEvent.Resume -> resume() - } - } +/** + * Fetches data for the [InfiniteQueryKey] using the value of [param]. + */ +suspend fun InfiniteQueryRef.loadMore(param: S) { + send(InfiniteQueryCommands.LoadMore(key, param)) } diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/MutationRef.kt b/soil-query-core/src/commonMain/kotlin/soil/query/MutationRef.kt index a9642ad..3dcac0a 100644 --- a/soil-query-core/src/commonMain/kotlin/soil/query/MutationRef.kt +++ b/soil-query-core/src/commonMain/kotlin/soil/query/MutationRef.kt @@ -4,48 +4,52 @@ package soil.query import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.flow.StateFlow +import soil.query.core.Actor import soil.query.core.toResultCallback /** - * A reference to a [Mutation] for [MutationKey]. + * A reference to a Mutation for [MutationKey]. * * @param T Type of the return value from the mutation. * @param S Type of the variable to be mutated. - * @property key Instance of a class implementing [MutationKey]. - * @param mutation The mutation to perform. - * @constructor Creates a [MutationRef]. */ -class MutationRef( - val key: MutationKey, - val options: MutationOptions, - mutation: Mutation -) : Mutation by mutation { +interface MutationRef : Actor { - /** - * Mutates the variable. - * - * @param variable The variable to be mutated. - * @return The result of the mutation. - */ - suspend fun mutate(variable: S): T { - val deferred = CompletableDeferred() - command.send(MutationCommands.Mutate(key, variable, state.value.revision, deferred.toResultCallback())) - return deferred.await() - } + val key: MutationKey + val options: MutationOptions + val state: StateFlow> /** - * Mutates the variable asynchronously. - * - * @param variable The variable to be mutated. + * Sends a [MutationCommand] to the Actor. */ - suspend fun mutateAsync(variable: S) { - command.send(MutationCommands.Mutate(key, variable, state.value.revision)) - } + suspend fun send(command: MutationCommand) +} - /** - * Resets the mutation state. - */ - suspend fun reset() { - command.send(MutationCommands.Reset()) - } +/** + * Mutates the variable. + * + * @param variable The variable to be mutated. + * @return The result of the mutation. + */ +suspend fun MutationRef.mutate(variable: S): T { + val deferred = CompletableDeferred() + send(MutationCommands.Mutate(key, variable, state.value.revision, deferred.toResultCallback())) + return deferred.await() +} + +/** + * Mutates the variable asynchronously. + * + * @param variable The variable to be mutated. + */ +suspend fun MutationRef.mutateAsync(variable: S) { + send(MutationCommands.Mutate(key, variable, state.value.revision)) +} + +/** + * Resets the mutation state. + */ +suspend fun MutationRef.reset() { + send(MutationCommands.Reset()) } diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/QueryRef.kt b/soil-query-core/src/commonMain/kotlin/soil/query/QueryRef.kt index 0807888..464857b 100644 --- a/soil-query-core/src/commonMain/kotlin/soil/query/QueryRef.kt +++ b/soil-query-core/src/commonMain/kotlin/soil/query/QueryRef.kt @@ -3,71 +3,39 @@ package soil.query -import kotlinx.coroutines.CompletableDeferred -import soil.query.core.toResultCallback -import kotlin.coroutines.cancellation.CancellationException +import kotlinx.coroutines.flow.StateFlow +import soil.query.core.Actor /** - * A reference to an [Query] for [QueryKey]. + * A reference to an Query for [QueryKey]. * * @param T Type of data to retrieve. - * @property key Instance of a class implementing [QueryKey]. - * @param query Transparently referenced [Query]. - * @constructor Creates an [QueryRef] */ -class QueryRef( - val key: QueryKey, - val options: QueryOptions, - query: Query -) : Query by query { +interface QueryRef : Actor { - /** - * Starts the [Query]. - * - * This function must be invoked when a new mount point (subscriber) is added. - */ - suspend fun start() { - command.send(QueryCommands.Connect(key)) - event.collect(::handleEvent) - } + val key: QueryKey + val options: QueryOptions + val state: StateFlow> /** - * Prefetches the [Query]. + * Sends a [QueryCommand] to the Actor. */ - suspend fun prefetch(): Boolean { - val deferred = CompletableDeferred() - command.send(QueryCommands.Connect(key, state.value.revision, deferred.toResultCallback())) - return try { - deferred.await() - true - } catch (e: CancellationException) { - throw e - } catch (e: Throwable) { - false - } - } - - /** - * Invalidates the [Query]. - * - * Calling this function will invalidate the retrieved data of the [Query], - * setting [QueryModel.isInvalidated] to `true` until revalidation is completed. - */ - suspend fun invalidate() { - command.send(QueryCommands.Invalidate(key, state.value.revision)) - } + suspend fun send(command: QueryCommand) +} - /** - * Resumes the [Query]. - */ - internal suspend fun resume() { - command.send(QueryCommands.Connect(key, state.value.revision)) - } +/** + * Invalidates the Query. + * + * Calling this function will invalidate the retrieved data of the Query, + * setting [QueryModel.isInvalidated] to `true` until revalidation is completed. + */ +suspend fun QueryRef.invalidate() { + send(QueryCommands.Invalidate(key, state.value.revision)) +} - private suspend fun handleEvent(e: QueryEvent) { - when (e) { - QueryEvent.Invalidate -> invalidate() - QueryEvent.Resume -> resume() - } - } +/** + * Resumes the Query. + */ +suspend fun QueryRef.resume() { + send(QueryCommands.Connect(key, state.value.revision)) } diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/SwrCache.kt b/soil-query-core/src/commonMain/kotlin/soil/query/SwrCache.kt index eac05dc..f721ade 100644 --- a/soil-query-core/src/commonMain/kotlin/soil/query/SwrCache.kt +++ b/soil-query-core/src/commonMain/kotlin/soil/query/SwrCache.kt @@ -212,7 +212,7 @@ class SwrCache(private val policy: SwrCachePolicy) : SwrClient, QueryMutableClie initialValue = MutationState() ).also { mutationStore[id] = it } } - return MutationRef( + return SwrMutation( key = key, options = options, mutation = mutation @@ -287,10 +287,10 @@ class SwrCache(private val policy: SwrCachePolicy) : SwrClient, QueryMutableClie initialValue = queryCache[key.id] as? QueryState ?: newQueryState(key) ).also { queryStore[id] = it } } - return QueryRef( + return SwrQuery( key = key, - query = query, - options = options + options = options, + query = query ) } @@ -387,10 +387,10 @@ class SwrCache(private val policy: SwrCachePolicy) : SwrClient, QueryMutableClie initialValue = queryCache[id] as? QueryState> ?: newInfiniteQueryState(key) ).also { queryStore[id] = it } } - return InfiniteQueryRef( + return SwrInfiniteQuery( key = key, - query = query, - options = options + options = options, + query = query ) } @@ -621,8 +621,8 @@ class SwrCache(private val policy: SwrCachePolicy) : SwrClient, QueryMutableClie override val command: SendChannel> ) : Mutation { - override fun launchIn(scope: CoroutineScope) { - actor.launchIn(scope) + override fun launchIn(scope: CoroutineScope): Job { + return actor.launchIn(scope) } fun close() { @@ -651,8 +651,8 @@ class SwrCache(private val policy: SwrCachePolicy) : SwrClient, QueryMutableClie override val command: SendChannel> ) : Query { - override fun launchIn(scope: CoroutineScope) { - actor.launchIn(scope) + override fun launchIn(scope: CoroutineScope): Job { + return actor.launchIn(scope) } fun close() { diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/SwrInfiniteQuery.kt b/soil-query-core/src/commonMain/kotlin/soil/query/SwrInfiniteQuery.kt new file mode 100644 index 0000000..20b8893 --- /dev/null +++ b/soil-query-core/src/commonMain/kotlin/soil/query/SwrInfiniteQuery.kt @@ -0,0 +1,56 @@ +// Copyright 2024 Soil Contributors +// SPDX-License-Identifier: Apache-2.0 + +package soil.query + +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import soil.query.core.toResultCallback + +internal class SwrInfiniteQuery( + override val key: InfiniteQueryKey, + override val options: QueryOptions, + private val query: Query> +) : InfiniteQueryRef { + + override val state: StateFlow>> + get() = query.state + + override fun launchIn(scope: CoroutineScope): Job { + return scope.launch { + query.launchIn(this) + query.event.collect(::handleEvent) + } + } + + override suspend fun send(command: QueryCommand>) { + query.command.send(command) + } + + private suspend fun handleEvent(e: QueryEvent) { + when (e) { + QueryEvent.Invalidate -> invalidate() + QueryEvent.Resume -> resume() + } + } +} + +/** + * Prefetches the Query. + */ +internal suspend fun InfiniteQueryRef.prefetch(): Boolean { + val deferred = CompletableDeferred>() + send(InfiniteQueryCommands.Connect(key, state.value.revision, deferred.toResultCallback())) + return try { + deferred.await() + true + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + false + } +} diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/SwrMutation.kt b/soil-query-core/src/commonMain/kotlin/soil/query/SwrMutation.kt new file mode 100644 index 0000000..3f6d565 --- /dev/null +++ b/soil-query-core/src/commonMain/kotlin/soil/query/SwrMutation.kt @@ -0,0 +1,26 @@ +// Copyright 2024 Soil Contributors +// SPDX-License-Identifier: Apache-2.0 + +package soil.query + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.StateFlow + +internal class SwrMutation( + override val key: MutationKey, + override val options: MutationOptions, + private val mutation: Mutation +) : MutationRef { + + override val state: StateFlow> + get() = mutation.state + + override fun launchIn(scope: CoroutineScope): Job { + return mutation.launchIn(scope) + } + + override suspend fun send(command: MutationCommand) { + mutation.command.send(command) + } +} diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/SwrQuery.kt b/soil-query-core/src/commonMain/kotlin/soil/query/SwrQuery.kt new file mode 100644 index 0000000..171676f --- /dev/null +++ b/soil-query-core/src/commonMain/kotlin/soil/query/SwrQuery.kt @@ -0,0 +1,56 @@ +// Copyright 2024 Soil Contributors +// SPDX-License-Identifier: Apache-2.0 + +package soil.query + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import soil.query.core.toResultCallback +import kotlin.coroutines.cancellation.CancellationException + +internal class SwrQuery( + override val key: QueryKey, + override val options: QueryOptions, + private val query: Query +) : QueryRef { + + override val state: StateFlow> + get() = query.state + + override fun launchIn(scope: CoroutineScope): Job { + return scope.launch { + query.launchIn(this) + query.event.collect(::handleEvent) + } + } + + override suspend fun send(command: QueryCommand) { + query.command.send(command) + } + + private suspend fun handleEvent(e: QueryEvent) { + when (e) { + QueryEvent.Invalidate -> invalidate() + QueryEvent.Resume -> resume() + } + } +} + +/** + * Prefetches the Query. + */ +internal suspend fun QueryRef.prefetch(): Boolean { + val deferred = CompletableDeferred() + send(QueryCommands.Connect(key, state.value.revision, deferred.toResultCallback())) + return try { + deferred.await() + true + } catch (e: CancellationException) { + throw e + } catch (e: Throwable) { + false + } +} diff --git a/soil-query-core/src/commonMain/kotlin/soil/query/core/Actor.kt b/soil-query-core/src/commonMain/kotlin/soil/query/core/Actor.kt index 0ea23ef..2aef81c 100644 --- a/soil-query-core/src/commonMain/kotlin/soil/query/core/Actor.kt +++ b/soil-query-core/src/commonMain/kotlin/soil/query/core/Actor.kt @@ -28,7 +28,7 @@ interface Actor { * * @param scope The scope in which the actor will run */ - fun launchIn(scope: CoroutineScope) + fun launchIn(scope: CoroutineScope): Job } internal typealias ActorSequenceNumber = Int @@ -48,9 +48,9 @@ internal class ActorBlockRunner( private var runningJob: Job? = null private var cancellationJob: Job? = null - override fun launchIn(scope: CoroutineScope) { + override fun launchIn(scope: CoroutineScope): Job { seq++ - scope.launch(start = CoroutineStart.UNDISPATCHED) { + return scope.launch(start = CoroutineStart.UNDISPATCHED) { cancellationJob?.cancelAndJoin() cancellationJob = null suspendCancellableCoroutine { continuation ->