Compare commits
1 Commits
538cfb9c5b
...
1228118027
| Author | SHA1 | Date | |
|---|---|---|---|
| 1228118027 |
@@ -15,6 +15,11 @@ export const REHYDRATE_API = 'api/rehydrate';
|
|||||||
export interface RehydrateApiPayload {
|
export interface RehydrateApiPayload {
|
||||||
queries: Record<string, unknown>;
|
queries: Record<string, unknown>;
|
||||||
mutations: Record<string, unknown>;
|
mutations: Record<string, unknown>;
|
||||||
|
// RTKQ's invalidation slice reads `provided.tags`/`provided.keys` during
|
||||||
|
// rehydration (it does `Object.entries(provided.tags ?? {})`), so `provided`
|
||||||
|
// must be an object — a bare `{ queries, mutations }` makes it crash on
|
||||||
|
// `provided.tags` of undefined. Always present; empty objects are valid.
|
||||||
|
provided: { tags: Record<string, unknown>; keys: Record<string, unknown> };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rehydrateApi = createAction<RehydrateApiPayload>(REHYDRATE_API);
|
export const rehydrateApi = createAction<RehydrateApiPayload>(REHYDRATE_API);
|
||||||
|
|||||||
@@ -22,13 +22,17 @@ type QueryEntry = ApiState['queries'][string];
|
|||||||
* carry no usable data and subscriptions are rebuilt by components on mount.
|
* carry no usable data and subscriptions are rebuilt by components on mount.
|
||||||
* Mutation results are never restored.
|
* Mutation results are never restored.
|
||||||
*/
|
*/
|
||||||
|
const EMPTY_PROVIDED = { tags: {}, keys: {} };
|
||||||
|
|
||||||
function snapshot(apiState: ApiState): RehydrateApiPayload {
|
function snapshot(apiState: ApiState): RehydrateApiPayload {
|
||||||
const queries: Record<string, unknown> = {};
|
const queries: Record<string, unknown> = {};
|
||||||
for (const [key, entry] of Object.entries(apiState.queries)) {
|
for (const [key, entry] of Object.entries(apiState.queries)) {
|
||||||
const q = entry as QueryEntry | undefined;
|
const q = entry as QueryEntry | undefined;
|
||||||
if (q && q.status === 'fulfilled') queries[key] = q;
|
if (q && q.status === 'fulfilled') queries[key] = q;
|
||||||
}
|
}
|
||||||
return { queries, mutations: {} };
|
// Carry `provided` along so RTKQ can re-register invalidation tags for the
|
||||||
|
// restored entries; it is also required structurally (see RehydrateApiPayload).
|
||||||
|
return { queries, mutations: {}, provided: apiState.provided ?? EMPTY_PROVIDED };
|
||||||
}
|
}
|
||||||
|
|
||||||
function load(): RehydrateApiPayload | null {
|
function load(): RehydrateApiPayload | null {
|
||||||
@@ -37,7 +41,13 @@ function load(): RehydrateApiPayload | null {
|
|||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
const parsed = JSON.parse(raw) as Partial<RehydrateApiPayload>;
|
const parsed = JSON.parse(raw) as Partial<RehydrateApiPayload>;
|
||||||
if (!parsed.queries) return null;
|
if (!parsed.queries) return null;
|
||||||
return { queries: parsed.queries, mutations: {} };
|
// `provided` may be absent in snapshots written before this field existed —
|
||||||
|
// default it so the invalidation slice doesn't crash on `provided.tags`.
|
||||||
|
return {
|
||||||
|
queries: parsed.queries,
|
||||||
|
mutations: {},
|
||||||
|
provided: parsed.provided ?? EMPTY_PROVIDED,
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,23 @@ test('rehydrateApiCache replays a stored cache as a rehydrate action', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('rehydrate payload always carries `provided` (regression: RTKQ reads provided.tags)', () => {
|
||||||
|
// A snapshot persisted before `provided` existed must not crash RTKQ's
|
||||||
|
// invalidation slice, which does `Object.entries(provided.tags ?? {})`.
|
||||||
|
instanceStorage.set(
|
||||||
|
'rtkq',
|
||||||
|
JSON.stringify({
|
||||||
|
queries: { 'getLibrary(undefined)': { status: 'fulfilled', data: [] } },
|
||||||
|
mutations: {},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const dispatched: Array<{ payload: { provided?: unknown } }> = [];
|
||||||
|
rehydrateApiCache((a) =>
|
||||||
|
dispatched.push(a as { payload: { provided?: unknown } }),
|
||||||
|
);
|
||||||
|
expect(dispatched[0].payload.provided).toEqual({ tags: {}, keys: {} });
|
||||||
|
});
|
||||||
|
|
||||||
test('startApiPersistence saves only fulfilled queries after throttle', () => {
|
test('startApiPersistence saves only fulfilled queries after throttle', () => {
|
||||||
rstest.useFakeTimers();
|
rstest.useFakeTimers();
|
||||||
let state = apiStateWith({});
|
let state = apiStateWith({});
|
||||||
|
|||||||
Reference in New Issue
Block a user