From fd0af472b381cb2e523c2726bde28bf659a704da Mon Sep 17 00:00:00 2001 From: jramosg Date: Thu, 20 Aug 2026 12:46:13 +0200 Subject: [PATCH 1/2] fix: keep cancelled initial queries retryable Fixes #4 --- CHANGELOG.md | 4 ++++ src/re_frame/query.cljc | 1 + src/re_frame/query/db.cljc | 4 ++++ test/re_frame/query/supersession_test.cljc | 24 ++++++++++++++++++++++ 4 files changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e6ea2..7b2f87d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Fixed +- Cancelling an in-flight initial query now leaves its cache entry stale so a + later `ensure-query` can retry it. + ## [0.12.0] - 2026-08-14 ### Added diff --git a/src/re_frame/query.cljc b/src/re_frame/query.cljc index 034934b..685d8c6 100644 --- a/src/re_frame/query.cljc +++ b/src/re_frame/query.cljc @@ -227,6 +227,7 @@ and `:refetch-state`. Responses from the superseded requests are dropped on arrival — `:data`, `:status` and `:error` are left as they are. No-op when the query is not cached. + An in-flight entry is marked stale so `ensure-query` can retry it. Use it when you write the cache yourself and do not want an older fetch to win the race: diff --git a/src/re_frame/query/db.cljc b/src/re_frame/query/db.cljc index cdb7fc3..e5e53d5 100644 --- a/src/re_frame/query/db.cljc +++ b/src/re_frame/query/db.cljc @@ -107,6 +107,9 @@ [db k params query-config request-id] (let [qid (util/query-id k params) query-data (get-in db [:re-frame.query/queries qid]) + in-flight? (or (:fetching? query-data) + (:fetching-next? query-data) + (:fetching-prev? query-data)) infinite? (util/infinite-query? query-config)] (when (and request-id (= (:request-id query-data) request-id)) @@ -122,6 +125,7 @@ util/merge-with-default (cond-> {:request-id request-id :fetching? false} + in-flight? (assoc :stale? true) infinite? (assoc :fetching-next? false :fetching-prev? false :refetch-state nil))) diff --git a/test/re_frame/query/supersession_test.cljc b/test/re_frame/query/supersession_test.cljc index 518cd2a..f1a1116 100644 --- a/test/re_frame/query/supersession_test.cljc +++ b/test/re_frame/query/supersession_test.cljc @@ -271,6 +271,30 @@ (is (nil? (query-entry qid)) "cancelling an uncached query does not create an entry")))) +(deftest cancel-query-allows-an-initial-request-to-retry + (testing "cancelling the initial load leaves the query stale" + (let [calls (reg-patients!) + qid (util/query-id :patients/page {:page 1})] + (h/process-event [:re-frame.query/ensure-query + :patients/page {:page 1}]) + (h/process-event [:re-frame.query/cancel-query + :patients/page {:page 1}]) + (let [query (query-entry qid)] + (is (= :loading (:status query))) + (is (false? (:fetching? query))) + (is (true? (:stale? query)) + "a cancelled initial request must remain retryable")) + + (h/process-event [:re-frame.query/ensure-query + :patients/page {:page 1}]) + (is (= 2 (count @calls)) + "ensure-query retries after the initial request is cancelled") + (deliver! (:on-success (attempt calls 0)) [{:id :cancelled}]) + (is (nil? (:data (query-entry qid))) + "the cancelled initial response is still dropped") + (deliver! (:on-success (attempt calls 1)) [{:id :fresh}]) + (is (= [{:id :fresh}] (:data (query-entry qid))))))) + (deftest cancel-query-only-clears-paging-flags-for-infinite-queries (testing "a cancelled regular query gains no infinite-only keys" (reg-patients!) From 2c248aa6afddf79db7bb62dbbf1d7d8cc3f0007b Mon Sep 17 00:00:00 2001 From: jramosg Date: Thu, 20 Aug 2026 12:48:07 +0200 Subject: [PATCH 2/2] Revert cancelled query to idle when never fetched Add compute-cancelled-status to determine the correct status after cancelling a query: if the query was :loading (no prior success), revert to :idle. Queries with a prior success or error retain their finished status. --- CHANGELOG.md | 2 ++ src/re_frame/query/db.cljc | 12 ++++++++++++ test/re_frame/query/supersession_test.cljc | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b2f87d..230ded3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed - Cancelling an in-flight initial query now leaves its cache entry stale so a later `ensure-query` can retry it. +- Cancelling a query that never received a successful response now reverts the + status to `:idle` instead of leaving it at `:loading`. ## [0.12.0] - 2026-08-14 diff --git a/src/re_frame/query/db.cljc b/src/re_frame/query/db.cljc index e5e53d5..596b642 100644 --- a/src/re_frame/query/db.cljc +++ b/src/re_frame/query/db.cljc @@ -89,6 +89,17 @@ (update db :re-frame.query/queries dissoc qid) db))) +(defn compute-cancelled-status + "Return the :status a query should have after cancellation. + + If the query never received a successful response (`:loading`), revert to + `:idle`. Otherwise keep the last finished status (`:success` or `:error`)." + [query-data] + (let [status (:status query-data)] + (if (= :loading status) + :idle + status))) + (defn cancel-query "Cancel any pending request attempts by setting `request-id` on the query data. Works by setting a new request-id on the query data, which will trigger the @@ -124,6 +135,7 @@ (update-in db [:re-frame.query/queries qid] util/merge-with-default (cond-> {:request-id request-id + :status (compute-cancelled-status query-data) :fetching? false} in-flight? (assoc :stale? true) infinite? (assoc :fetching-next? false diff --git a/test/re_frame/query/supersession_test.cljc b/test/re_frame/query/supersession_test.cljc index f1a1116..996e279 100644 --- a/test/re_frame/query/supersession_test.cljc +++ b/test/re_frame/query/supersession_test.cljc @@ -280,7 +280,8 @@ (h/process-event [:re-frame.query/cancel-query :patients/page {:page 1}]) (let [query (query-entry qid)] - (is (= :loading (:status query))) + (is (= :idle (:status query)) + "cancel reverts to :idle when there was no previous success") (is (false? (:fetching? query))) (is (true? (:stale? query)) "a cancelled initial request must remain retryable"))