From fc009279d81f95b856a4f56e80d57024bb06402f Mon Sep 17 00:00:00 2001 From: ShacharHarshuv Date: Mon, 29 Apr 2024 10:49:14 -0400 Subject: [PATCH] fix(angular-query-experimental): allow returning undefined in initialData function Fix incorrect types that disallowed that. This is necessary because the initialData function can typically read cache synchronously and need to be able to signal to the query if there is a cache miss and an actual fetch needs to be deployed. No breaking changes, any code that worked before should work now as well. --- .../src/__tests__/query-options.test-d.ts | 171 ++++++++++-------- .../src/query-options.ts | 29 +-- 2 files changed, 112 insertions(+), 88 deletions(-) diff --git a/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts b/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts index 4afe19c2af5..24642c0947f 100644 --- a/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts +++ b/packages/angular-query-experimental/src/__tests__/query-options.test-d.ts @@ -1,127 +1,146 @@ -import { assertType, describe, expectTypeOf } from 'vitest' -import { QueryClient } from '@tanstack/query-core' -import { dataTagSymbol } from '@tanstack/query-core' -import { queryOptions } from '../query-options' -import { injectQuery } from '../inject-query' -import type { Signal } from '@angular/core' - -describe('queryOptions', () => { - test('should not allow excess properties', () => { +import { assertType, describe, expectTypeOf } from "vitest"; +import { QueryClient } from "@tanstack/query-core"; +import { dataTagSymbol } from "@tanstack/query-core"; +import { queryOptions } from "../query-options"; +import { injectQuery } from "../inject-query"; +import type { Signal } from "@angular/core"; + +describe("queryOptions", () => { + test("should not allow excess properties", () => { return queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), // @ts-expect-error this is a good error, because stallTime does not exist! stallTime: 1000, - }) - }) + }); + }); - test('should infer types for callbacks', () => { + test("should infer types for callbacks", () => { return queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), staleTime: 1000, select: (data) => { - expectTypeOf(data).toEqualTypeOf() + expectTypeOf(data).toEqualTypeOf(); }, - }) - }) -}) - -test('should work when passed to injectQuery', () => { + }); + }); + + test("should allow undefined response in initialData", () => { + return (id: string | null) => + queryOptions({ + queryKey: ["todo", id], + queryFn: () => + Promise.resolve({ + id: "1", + title: "Do Laundry", + }), + initialData: () => + !id + ? undefined + : { + id, + title: "Initial Data", + }, + }); + }); +}); + +test("should work when passed to injectQuery", () => { const options = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), - }) + }); - const { data } = injectQuery(() => options) - expectTypeOf(data).toEqualTypeOf>() -}) + const { data } = injectQuery(() => options); + expectTypeOf(data).toEqualTypeOf>(); +}); -test('should work when passed to fetchQuery', () => { +test("should work when passed to fetchQuery", () => { const options = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), - }) + }); - const data = new QueryClient().fetchQuery(options) - assertType>(data) -}) + const data = new QueryClient().fetchQuery(options); + assertType>(data); +}); -test('should tag the queryKey with the result type of the QueryFn', () => { +test("should tag the queryKey with the result type of the QueryFn", () => { const { queryKey } = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), - }) - assertType(queryKey[dataTagSymbol]) -}) + }); + assertType(queryKey[dataTagSymbol]); +}); -test('should tag the queryKey even if no promise is returned', () => { +test("should tag the queryKey even if no promise is returned", () => { const { queryKey } = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => 5, - }) - assertType(queryKey[dataTagSymbol]) -}) + }); + assertType(queryKey[dataTagSymbol]); +}); -test('should tag the queryKey with unknown if there is no queryFn', () => { +test("should tag the queryKey with unknown if there is no queryFn", () => { const { queryKey } = queryOptions({ - queryKey: ['key'], - }) + queryKey: ["key"], + }); - assertType(queryKey[dataTagSymbol]) -}) + assertType(queryKey[dataTagSymbol]); +}); -test('should tag the queryKey with the result type of the QueryFn if select is used', () => { +test("should tag the queryKey with the result type of the QueryFn if select is used", () => { const { queryKey } = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), select: (data) => data.toString(), - }) + }); - assertType(queryKey[dataTagSymbol]) -}) + assertType(queryKey[dataTagSymbol]); +}); -test('should return the proper type when passed to getQueryData', () => { +test("should return the proper type when passed to getQueryData", () => { const { queryKey } = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), - }) + }); - const queryClient = new QueryClient() - const data = queryClient.getQueryData(queryKey) + const queryClient = new QueryClient(); + const data = queryClient.getQueryData(queryKey); - expectTypeOf(data).toEqualTypeOf() -}) + expectTypeOf(data).toEqualTypeOf(); +}); -test('should properly type updaterFn when passed to setQueryData', () => { +test("should properly type updaterFn when passed to setQueryData", () => { const { queryKey } = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), - }) + }); - const queryClient = new QueryClient() + const queryClient = new QueryClient(); const data = queryClient.setQueryData(queryKey, (prev) => { - expectTypeOf(prev).toEqualTypeOf() - return prev - }) + expectTypeOf(prev).toEqualTypeOf(); + return prev; + }); - expectTypeOf(data).toEqualTypeOf() -}) + expectTypeOf(data).toEqualTypeOf(); +}); -test('should properly type value when passed to setQueryData', () => { +test("should properly type value when passed to setQueryData", () => { const { queryKey } = queryOptions({ - queryKey: ['key'], + queryKey: ["key"], queryFn: () => Promise.resolve(5), - }) + }); - const queryClient = new QueryClient() + const queryClient = new QueryClient(); // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, '5') + queryClient.setQueryData(queryKey, "5"); // @ts-expect-error value should be a number - queryClient.setQueryData(queryKey, () => '5') + queryClient.setQueryData(queryKey, () => "5"); - const data = queryClient.setQueryData(queryKey, 5) + const data = queryClient.setQueryData(queryKey, 5); - expectTypeOf(data).toEqualTypeOf() -}) + expectTypeOf(data).toEqualTypeOf(); +}); diff --git a/packages/angular-query-experimental/src/query-options.ts b/packages/angular-query-experimental/src/query-options.ts index 7e958f3df78..a547109702b 100644 --- a/packages/angular-query-experimental/src/query-options.ts +++ b/packages/angular-query-experimental/src/query-options.ts @@ -1,5 +1,10 @@ -import type { DataTag, DefaultError, QueryKey } from '@tanstack/query-core' -import type { CreateQueryOptions } from './types' +import { + DataTag, + DefaultError, + QueryKey, + InitialDataFunction, +} from "@tanstack/query-core"; +import type { CreateQueryOptions } from "./types"; export type UndefinedInitialDataOptions< TQueryFnData = unknown, @@ -7,10 +12,10 @@ export type UndefinedInitialDataOptions< TData = TQueryFnData, TQueryKey extends QueryKey = QueryKey, > = CreateQueryOptions & { - initialData?: undefined -} + initialData?: undefined; +}; -type NonUndefinedGuard = T extends undefined ? never : T +type NonUndefinedGuard = T extends undefined ? never : T; export type DefinedInitialDataOptions< TQueryFnData = unknown, @@ -20,8 +25,8 @@ export type DefinedInitialDataOptions< > = CreateQueryOptions & { initialData: | NonUndefinedGuard - | (() => NonUndefinedGuard) -} + | InitialDataFunction>; +}; export function queryOptions< TQueryFnData = unknown, @@ -31,8 +36,8 @@ export function queryOptions< >( options: UndefinedInitialDataOptions, ): UndefinedInitialDataOptions & { - queryKey: DataTag -} + queryKey: DataTag; +}; export function queryOptions< TQueryFnData = unknown, @@ -42,9 +47,9 @@ export function queryOptions< >( options: DefinedInitialDataOptions, ): DefinedInitialDataOptions & { - queryKey: DataTag -} + queryKey: DataTag; +}; export function queryOptions(options: unknown) { - return options + return options; }