query-db-collection: a later refetch failure of one on-demand subset never reaches that subset's live query

Offen
#2,270 0 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
48/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Aktiv
Tech-Stack
typescript
Bereich
database

Rechercherichtung

Start with the supplied Vitest reproduction and trace how query-db-collection records refetch failures around query.ts:3265-3280. Then follow how loadSubset:error reaches lastSubsetError in collection-subscriber.ts:108-116, and inspect the related work in #1764. Done when the owning live query exposes a failed refresh, retains its rows, and other subsets remain unaffected.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

  • I've validated the bug against the latest version of DB packages

Describe the bug

With syncMode: "on-demand", once a subset has loaded, a later refetch of that subset that fails (e.g. after queryClient.invalidateQueries) is recorded only collection-wide (collection.utils.lastError). The live query that owns the subset stays ready, its utils.lastSubsetError stays undefined, and nothing tells it the data on screen could not be refreshed.

#1764 (fixed by #1756 / #1797) scoped failures of a subset's first load to the requesting subscription. This is the same expectation for a later load of a subset that already succeeded once.

Tested with @tanstack/db 0.15.2, @tanstack/query-db-collection 1.4.8, @tanstack/query-core 5.104.5 (no related commits on main since 0.15.2).

To Reproduce

Drop-in vitest test (no framework adapter):

import { expect, test } from "vitest";
import { createCollection, createLiveQueryCollection, eq } from "@tanstack/db";
import { QueryClient } from "@tanstack/query-core";
import { parseLoadSubsetOptions, queryCollectionOptions } from "@tanstack/query-db-collection";

const tick = () => new Promise((resolve) => setTimeout(resolve, 0));
const settle = async () => {
  for (let i = 0; i < 30; i++) await tick();
};

test("a later refetch failure is visible on the failed subset's live query, not just collection-wide", async () => {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 60_000 } } });
  let failing = null;
  const reads = [];
  const collection = createCollection(queryCollectionOptions({
    id: "gap-1",
    queryClient,
    queryKey: ["gap-1"],
    syncMode: "on-demand",
    getKey: (row) => row.key,
    queryFn: (ctx) => {
      const { filters } = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions);
      const seq = filters.find((filter) => filter.field.join(".") === "seq")?.value;
      reads.push(seq);
      if (seq === failing) return Promise.reject(new Error("read failed"));
      return Promise.resolve([{ key: seq + "-1", seq }, { key: seq + "-2", seq }]);
    },
  }));
  const live = (seq) => createLiveQueryCollection((q) => q.from({ r: collection }).where(({ r }) => eq(r.seq, seq)));
  const a = live("a");
  const b = live("b");
  await Promise.all([a.preload(), b.preload()]);
  expect(a.toArray.map((row) => row.seq)).toEqual(["a", "a"]);

  failing = "a";
  await queryClient.invalidateQueries({ queryKey: ["gap-1"] });
  await settle();

  expect(reads.filter((seq) => seq === "a")).toHaveLength(2);
  expect(a.toArray.map((row) => row.seq)).toEqual(["a", "a"]);
  expect(collection.utils.lastError).toBeInstanceOf(Error);
  expect(a.status === "error" || a.utils.lastSubsetError instanceof Error).toBe(true);
  expect(b.status).toBe("ready");
  expect(b.utils.lastSubsetError).toBeUndefined();
});

Result: the last two a assertions fail; collection.utils.lastError is set.

Expected behavior

The failed subset's live query exposes the refetch failure (error status, or at least utils.lastSubsetError), while its rows stay (a failed refresh should not empty the view) and other subsets' live queries are unaffected.

Additional context

  • The error is recorded collection-wide at query-db-collection/src/query.ts:3265-3266, and marks the collection only while it is loading (:3278-3280). lastSubsetError is fed only by loadSubset:error (db/src/query/live/collection-subscriber.ts:108-116), which an observer refetch failure never emits.
  • Related: #672 (first-class error tracking), #1764.
  • Why it matters: an app showing paged server data (ratings, history) cannot tell a member "couldn't refresh, retry" for one view. We currently read the query cache directly to find a failed subset's error.
  • Happy to send a PR (with the oracle coverage CONTRIBUTING asks for) if you'd like to assign it; equally fine if you'd rather fix it in your own flow. The test above is free to adopt as-is.

The repro was written with an AI coding assistant and checked by hand against the installed sources.

Vorherrschende Sprache
TypeScript
Sterne
4k
Forks
277
Ø Merge
1 T. 7 Std.
Gemergte PRs (30 T.)
172

Entwicklungsumgebung

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus TanStack/db

Alle Issues in TanStack/db

Ähnliche Issues

Weitere Issues zu TypeScript

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.