refactor(api): share tracking-dependent query key lists between web and native

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Opus 5.5 committed 2026-10-05 04:27:43 -04:00
1 parent 88e777d5ce
commit 778a1356d5
4 files changed
+47 -19

No files matched your search

+13 -11
View File
@@ -4,20 +4,23 @@ import { client, orpc } from "@/lib/orpc";
import { queryClient } from "@/lib/query-client";
import { toast } from "@/lib/toast";
import { refreshWidgets } from "@/lib/widgets";
import { trackingDerivedQueryKeys, trackingStateQueryKeys } from "@sofa/api/query-keys";
import { i18n } from "@sofa/i18n";
let widgetRefreshTimer: ReturnType<typeof setTimeout> | null = null;
/**
* Mark every query that depends on the user's tracking state as stale. Title details
* (`titles.get`) are excluded — they don't depend on tracking (same rule as the web's
* invalidateTrackingQueries). Resolves once tracking and library queries have refetched,
* so mutations can stay pending until the UI reflects the change.
* Mark every query that depends on the user's tracking state as stale — the shared
* `trackingStateQueryKeys` and `trackingDerivedQueryKeys` lists from `@sofa/api/query-keys`.
* Title details (`titles.get`) are excluded — they don't depend on tracking (same rule as the
* web's invalidateTrackingQueries). Derived queries are fire-and-forget; resolves once the
* tracking-state queries (tracking + library) have refetched, so mutations can stay pending
* until the UI reflects the change.
*/
export function invalidateTitleQueries(): Promise<unknown> {
void queryClient.invalidateQueries({ queryKey: orpc.discover.key() });
void queryClient.invalidateQueries({ queryKey: orpc.people.key() });
void queryClient.invalidateQueries({ queryKey: orpc.titles.similar.key() });
for (const queryKey of trackingDerivedQueryKeys(orpc)) {
void queryClient.invalidateQueries({ queryKey });
}
// Debounce widget refresh to batch rapid mutations (e.g. watching multiple episodes)
if (widgetRefreshTimer) clearTimeout(widgetRefreshTimer);
@@ -26,10 +29,9 @@ export function invalidateTitleQueries(): Promise<unknown> {
widgetRefreshTimer = null;
}, 2000);
return Promise.all([
queryClient.invalidateQueries({ queryKey: orpc.tracking.key() }),
queryClient.invalidateQueries({ queryKey: orpc.library.key() }),
]);
return Promise.all(
trackingStateQueryKeys(orpc).map((queryKey) => queryClient.invalidateQueries({ queryKey })),
);
}
/**
+7 -7
View File
@@ -1,5 +1,7 @@
import type { QueryClient } from "@tanstack/react-query";
import { trackingDerivedQueryKeys, trackingStateQueryKeys } from "@sofa/api/query-keys";
import { orpc } from "./client";
/**
@@ -9,11 +11,9 @@ import { orpc } from "./client";
* (`titles.get`) are deliberately excluded — they don't depend on tracking.
*/
export function invalidateTrackingQueries(queryClient: QueryClient): Promise<unknown> {
return Promise.all([
queryClient.invalidateQueries({ queryKey: orpc.tracking.key() }),
queryClient.invalidateQueries({ queryKey: orpc.library.key() }),
queryClient.invalidateQueries({ queryKey: orpc.discover.key() }),
queryClient.invalidateQueries({ queryKey: orpc.people.key() }),
queryClient.invalidateQueries({ queryKey: orpc.titles.similar.key() }),
]);
return Promise.all(
[...trackingStateQueryKeys(orpc), ...trackingDerivedQueryKeys(orpc)].map((queryKey) =>
queryClient.invalidateQueries({ queryKey }),
),
);
}
+2 -1
View File
@@ -7,7 +7,8 @@
"./contract": "./src/contract.ts",
"./schemas": "./src/schemas.ts",
"./errors": "./src/errors.ts",
"./utils": "./src/utils.ts"
"./utils": "./src/utils.ts",
"./query-keys": "./src/query-keys.ts"
},
"scripts": {
"lint": "oxlint",
+25
View File
@@ -0,0 +1,25 @@
type Keyed = { key(): readonly unknown[] };
type TrackingKeyedClient = {
tracking: Keyed;
library: Keyed;
discover: Keyed;
people: Keyed;
titles: { similar: Keyed };
};
/**
* Queries that directly show the user's tracking state (statuses, watches, ratings, library).
* Mutations can wait on these so the UI reflects the change before they settle.
*/
export function trackingStateQueryKeys(orpc: TrackingKeyedClient): (readonly unknown[])[] {
return [orpc.tracking.key(), orpc.library.key()];
}
/**
* Queries whose results are derived from tracking state (recommendations, discover statuses,
* similar titles, people credits). Refetch them after a change, but they can be slow (TMDB-backed).
* `titles.get` is deliberately absent — title details don't depend on tracking.
*/
export function trackingDerivedQueryKeys(orpc: TrackingKeyedClient): (readonly unknown[])[] {
return [orpc.discover.key(), orpc.people.key(), orpc.titles.similar.key()];
}