Skip to content

Commit dfa657e

Browse files
ntuckercursoragent
andauthored
fix(core): treat only undefined as missing for passthrough endpoint cache (#3960)
* fix(core): treat only undefined as missing for passthrough endpoint cache getResponseMeta used !cacheEndpoints, so falsy cached values ('', 0, false, null) were marked Invalid and hooks refetched indefinitely. Use cacheEndpoints === undefined to match shouldQuery semantics. Add regression tests for schema-less endpoints; changeset for @data-client/core. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: add linked packages to falsy-cache changeset Co-authored-by: Cursor <cursoragent@cursor.com> * chore: rephrase changeset from user-facing outcome Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 26278bd commit dfa657e

3 files changed

Lines changed: 56 additions & 1 deletion

File tree

.changeset/giant-dingos-wink.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@data-client/core': patch
3+
'@data-client/normalizr': patch
4+
'@data-client/react': patch
5+
'@data-client/vue': patch
6+
---
7+
8+
Endpoints that resolve to falsy values (`''`, `0`, `false`, or `null`) no longer trigger infinite refetches.

packages/core/src/controller/Controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ export default class Controller<
529529
return {
530530
data: cacheEndpoints,
531531
expiryStatus: this.getExpiryStatus(
532-
!cacheEndpoints,
532+
cacheEndpoints === undefined,
533533
!!endpoint.invalidIfStale,
534534
meta,
535535
),

packages/core/src/controller/__tests__/getResponse.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,53 @@ describe('Controller.getResponse() with Scalar', () => {
318318
);
319319
});
320320

321+
describe('Controller.getResponse() with schema-less endpoints', () => {
322+
it.each([
323+
['empty string', ''],
324+
['zero', 0],
325+
['false', false],
326+
['null', null],
327+
])('treats cached %s as valid data', (_name, cachedValue) => {
328+
const controller = new Contoller();
329+
const endpoint = new Endpoint(() => Promise.resolve(), {
330+
key: () => 'falsy-endpoint',
331+
});
332+
const key = endpoint.key();
333+
const fetchedAt = Date.now();
334+
const state = {
335+
...initialState,
336+
endpoints: {
337+
[key]: cachedValue,
338+
},
339+
meta: {
340+
[key]: {
341+
date: fetchedAt,
342+
fetchedAt,
343+
expiresAt: fetchedAt + 1000,
344+
},
345+
},
346+
};
347+
348+
const { data, expiryStatus } = controller.getResponse(endpoint, state);
349+
expect(data).toBe(cachedValue);
350+
expect(expiryStatus).toBe(ExpiryStatus.Valid);
351+
});
352+
353+
it('treats undefined cache entries as invalid', () => {
354+
const controller = new Contoller();
355+
const endpoint = new Endpoint(() => Promise.resolve(), {
356+
key: () => 'missing-endpoint',
357+
});
358+
359+
const { data, expiryStatus } = controller.getResponse(
360+
endpoint,
361+
initialState,
362+
);
363+
expect(data).toBeUndefined();
364+
expect(expiryStatus).toBe(ExpiryStatus.Invalid);
365+
});
366+
});
367+
321368
describe('Snapshot.getResponseMeta()', () => {
322369
it('denormalizes schema with extra members but not set', () => {
323370
const controller = new Contoller();

0 commit comments

Comments
 (0)