From 1da6588049480a27515b511171d5509edd3b0ec9 Mon Sep 17 00:00:00 2001 From: Vicary A Date: Tue, 4 Mar 2025 16:57:45 +0800 Subject: [PATCH] fix(package/gqty): useTransactionQuery should work without suspense --- .changeset/wise-dingos-doubt.md | 5 + examples/gnt/app/useTransactionQuery/page.tsx | 56 ++++++++ packages/react/package.json | 9 +- .../react/src/query/useTransactionQuery.ts | 7 + .../react/test/useTransactionQuery.test.tsx | 134 ++++++++++++++++++ pnpm-lock.yaml | 133 +++++++++-------- 6 files changed, 282 insertions(+), 62 deletions(-) create mode 100644 .changeset/wise-dingos-doubt.md create mode 100644 examples/gnt/app/useTransactionQuery/page.tsx create mode 100644 packages/react/test/useTransactionQuery.test.tsx diff --git a/.changeset/wise-dingos-doubt.md b/.changeset/wise-dingos-doubt.md new file mode 100644 index 000000000..88adaa1ff --- /dev/null +++ b/.changeset/wise-dingos-doubt.md @@ -0,0 +1,5 @@ +--- +'@gqty/react': patch +--- + +useTransactionQuery should work without suspense diff --git a/examples/gnt/app/useTransactionQuery/page.tsx b/examples/gnt/app/useTransactionQuery/page.tsx new file mode 100644 index 000000000..b75a0c32a --- /dev/null +++ b/examples/gnt/app/useTransactionQuery/page.tsx @@ -0,0 +1,56 @@ +'use client'; + +import { useState } from 'react'; +import { useTransactionQuery } from '~/gqty/react'; + +export default function UseTransactionQuery() { + const [skip, setSkip] = useState(false); + const { data, isLoading, error } = useTransactionQuery( + (query) => { + return query + .characters({ + filter: { + name: 'Rick', + }, + }) + ?.results?.map((character) => ({ + id: character?.id, + name: character?.name, + })); + }, + { skip } + ); + + return ( +
+

Use Transaction Query

+ + setSkip(!skip)} + /> + + + + {error &&
Error: {error.message}
} + + {!data?.[0]?.id && isLoading &&
Loading ...
} + + {data?.[0]?.id && ( +
    + {data.map((character) => ( +
  1. {character?.name}
  2. + ))} +
+ )} +
+ ); +} diff --git a/packages/react/package.json b/packages/react/package.json index 66ba78f14..1df1640bd 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -102,8 +102,8 @@ "@types/node": "^22.13.4", "@types/react": "^18.3.18", "@types/use-sync-external-store": "^0.0.6", - "@typescript-eslint/eslint-plugin": "^8.24.1", - "@typescript-eslint/parser": "^8.24.1", + "@typescript-eslint/eslint-plugin": "^8.26.0", + "@typescript-eslint/parser": "^8.26.0", "eslint": "^9.20.1", "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", "gqty": "workspace:^", @@ -115,10 +115,11 @@ "lodash-es": "^4.17.21", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-error-boundary": "^5.0.0", "react-test-renderer": "^18.3.1", "test-utils": "workspace:^", - "type-fest": "^4.35.0", - "typescript-eslint": "^8.24.1" + "type-fest": "^4.37.0", + "typescript-eslint": "^8.26.0" }, "peerDependencies": { "gqty": "workspace:^3.4.1", diff --git a/packages/react/src/query/useTransactionQuery.ts b/packages/react/src/query/useTransactionQuery.ts index 835aa9be7..7890a28ff 100644 --- a/packages/react/src/query/useTransactionQuery.ts +++ b/packages/react/src/query/useTransactionQuery.ts @@ -4,6 +4,7 @@ import { type GQtyError, type RetryOptions, } from 'gqty'; +import { useEffect } from 'react'; import { translateFetchPolicy, type LegacyFetchPolicy, @@ -106,6 +107,12 @@ export function createUseTransactionQuery( } }, [query.$state.error]); + useEffect(() => { + if (!skip) { + query.$refetch(false); + } + }, [skip]); + return skip ? { isCalled: false, diff --git a/packages/react/test/useTransactionQuery.test.tsx b/packages/react/test/useTransactionQuery.test.tsx new file mode 100644 index 000000000..fdf9c5978 --- /dev/null +++ b/packages/react/test/useTransactionQuery.test.tsx @@ -0,0 +1,134 @@ +import { render, renderHook, waitFor } from '@testing-library/react'; +import { GQtyError } from 'gqty'; +import React, { act, Suspense } from 'react'; +import ReactDOM from 'react-dom/client'; +import { ErrorBoundary } from 'react-error-boundary'; +import { createReactTestClient } from './utils'; + +describe('useTransactionQuery', () => { + it('should fetch without suspense', async () => { + const { useTransactionQuery } = await createReactTestClient(); + + const { result } = renderHook(() => + useTransactionQuery((query) => query.hello, { suspense: false }) + ); + + expect(result.current.data).toBeUndefined(); + await waitFor(() => expect(result.current.data).toBe('hello world')); + }); + + it('should fetch with suspense', async () => { + const { useTransactionQuery } = await createReactTestClient(); + + const MyComponent = () => { + const { data, error } = useTransactionQuery((query) => query.hello, { + suspense: true, + }); + + return ( + <> +
{error?.message}
+
{data}
+ + ); + }; + + const screen = render( + + + + ); + + await waitFor(() => expect(screen.getByText('Loading...')).toBeDefined()); + await waitFor(() => expect(screen.getByText('hello world')).toBeDefined()); + + expect(screen.getByTestId('error').textContent).toBe(''); + }); + + it('should handle errors without suspense', async () => { + const { useTransactionQuery } = await createReactTestClient( + undefined, + async () => { + throw new GQtyError('Network error'); + } + ); + const onError = jest.fn(); + + const { result } = renderHook(() => + useTransactionQuery((query) => query.hello, { suspense: false, onError }) + ); + + await waitFor(() => { + // expect(result.current.error).toBeDefined(); + expect(result.current.error?.message).toBe('Network error'); + }); + expect(onError).toHaveBeenCalled(); + }); + + it.skip('should handle errors with suspense', async () => { + const { useTransactionQuery } = await createReactTestClient( + undefined, + async () => { + console.log('fetcher called'); + throw new GQtyError('Network error'); + } + ); + const onError = jest.fn(); + + const MyComponent = () => { + const { data, error } = useTransactionQuery((query) => query.hello, { + suspense: true, + onError, + }); + + return ( + <> +
{error?.message}
+
{data}
+ + ); + }; + + let container: HTMLDivElement | null = null; + try { + container = document.createElement('div'); + document.body.appendChild(container); + + act(() => { + ReactDOM.createRoot(container!).render( + error.message}> + + + + + ); + }); + + await waitFor(() => expect(container?.innerText).toBe('Network error')); + // expect(screen.getByTestId('data').textContent).toBe(''); + expect(onError).toHaveBeenCalled(); + } finally { + if (container) { + document.body.removeChild(container); + } + } + }); + + it('should skip fetching when skip is true', async () => { + const mockFetch = jest.fn(); + const { useTransactionQuery } = await createReactTestClient( + undefined, + async (payload) => { + mockFetch(payload); + return {}; + } + ); + + renderHook(() => + useTransactionQuery((query) => query.hello, { skip: true }) + ); + + await new Promise((r) => setTimeout(r, 100)); + expect(mockFetch).not.toHaveBeenCalled(); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d2a07c17..ef9239510 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -508,10 +508,10 @@ importers: version: 2.7.3 '@typescript-eslint/eslint-plugin': specifier: ^8.24.1 - version: 8.24.1(@typescript-eslint/parser@8.24.1)(eslint@9.20.1)(typescript@5.7.3) + version: 8.26.0(@typescript-eslint/parser@8.26.0)(eslint@9.20.1)(typescript@5.7.3) '@typescript-eslint/parser': specifier: ^8.24.1 - version: 8.24.1(eslint@9.20.1)(typescript@5.7.3) + version: 8.26.0(eslint@9.20.1)(typescript@5.7.3) eslint: specifier: ^9.20.1 version: 9.20.1 @@ -573,10 +573,10 @@ importers: version: 8.5.14 '@typescript-eslint/eslint-plugin': specifier: ^8.24.1 - version: 8.24.1(@typescript-eslint/parser@8.24.1)(eslint@9.20.1)(typescript@5.7.3) + version: 8.26.0(@typescript-eslint/parser@8.26.0)(eslint@9.20.1)(typescript@5.7.3) '@typescript-eslint/parser': specifier: ^8.24.1 - version: 8.24.1(eslint@9.20.1)(typescript@5.7.3) + version: 8.26.0(eslint@9.20.1)(typescript@5.7.3) bob-esbuild-cli: specifier: ^4.0.0 version: 4.0.0(bob-esbuild@4.0.3) @@ -708,11 +708,11 @@ importers: specifier: ^0.0.6 version: 0.0.6 '@typescript-eslint/eslint-plugin': - specifier: ^8.24.1 - version: 8.24.1(@typescript-eslint/parser@8.24.1)(eslint@9.20.1)(typescript@5.7.3) + specifier: ^8.26.0 + version: 8.26.0(@typescript-eslint/parser@8.26.0)(eslint@9.20.1)(typescript@5.7.3) '@typescript-eslint/parser': - specifier: ^8.24.1 - version: 8.24.1(eslint@9.20.1)(typescript@5.7.3) + specifier: ^8.26.0 + version: 8.26.0(eslint@9.20.1)(typescript@5.7.3) eslint: specifier: ^9.20.1 version: 9.20.1 @@ -746,6 +746,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + react-error-boundary: + specifier: ^5.0.0 + version: 5.0.0(react@18.3.1) react-test-renderer: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) @@ -753,11 +756,11 @@ importers: specifier: workspace:^ version: link:../../internal/test-utils type-fest: - specifier: ^4.35.0 - version: 4.35.0 + specifier: ^4.37.0 + version: 4.37.0 typescript-eslint: - specifier: ^8.24.1 - version: 8.24.1(eslint@9.20.1)(typescript@5.7.3) + specifier: ^8.26.0 + version: 8.26.0(eslint@9.20.1)(typescript@5.7.3) publishDirectory: dist packages/solid: @@ -6285,20 +6288,20 @@ packages: - supports-color dev: true - /@typescript-eslint/eslint-plugin@8.24.1(@typescript-eslint/parser@8.24.1)(eslint@9.20.1)(typescript@5.7.3): - resolution: {integrity: sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==} + /@typescript-eslint/eslint-plugin@8.26.0(@typescript-eslint/parser@8.26.0)(eslint@9.20.1)(typescript@5.7.3): + resolution: {integrity: sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.24.1(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/scope-manager': 8.24.1 - '@typescript-eslint/type-utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.1 + '@typescript-eslint/parser': 8.26.0(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/type-utils': 8.26.0(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.26.0(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.26.0 eslint: 9.20.1 graphemer: 1.4.0 ignore: 5.3.2 @@ -6327,17 +6330,17 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@8.24.1(eslint@9.20.1)(typescript@5.7.3): - resolution: {integrity: sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==} + /@typescript-eslint/parser@8.26.0(eslint@9.20.1)(typescript@5.7.3): + resolution: {integrity: sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: - '@typescript-eslint/scope-manager': 8.24.1 - '@typescript-eslint/types': 8.24.1 - '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) - '@typescript-eslint/visitor-keys': 8.24.1 + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.7.3) + '@typescript-eslint/visitor-keys': 8.26.0 debug: 4.4.0 eslint: 9.20.1 typescript: 5.7.3 @@ -6353,12 +6356,12 @@ packages: '@typescript-eslint/visitor-keys': 8.24.0 dev: true - /@typescript-eslint/scope-manager@8.24.1: - resolution: {integrity: sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==} + /@typescript-eslint/scope-manager@8.26.0: + resolution: {integrity: sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.24.1 - '@typescript-eslint/visitor-keys': 8.24.1 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/visitor-keys': 8.26.0 dev: true /@typescript-eslint/type-utils@8.24.0(eslint@9.20.1)(typescript@5.7.3): @@ -6378,15 +6381,15 @@ packages: - supports-color dev: true - /@typescript-eslint/type-utils@8.24.1(eslint@9.20.1)(typescript@5.7.3): - resolution: {integrity: sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==} + /@typescript-eslint/type-utils@8.26.0(eslint@9.20.1)(typescript@5.7.3): + resolution: {integrity: sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: - '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.7.3) + '@typescript-eslint/utils': 8.26.0(eslint@9.20.1)(typescript@5.7.3) debug: 4.4.0 eslint: 9.20.1 ts-api-utils: 2.0.1(typescript@5.7.3) @@ -6400,8 +6403,8 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true - /@typescript-eslint/types@8.24.1: - resolution: {integrity: sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==} + /@typescript-eslint/types@8.26.0: + resolution: {integrity: sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dev: true @@ -6424,14 +6427,14 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree@8.24.1(typescript@5.7.3): - resolution: {integrity: sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==} + /@typescript-eslint/typescript-estree@8.26.0(typescript@5.7.3): + resolution: {integrity: sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: - '@typescript-eslint/types': 8.24.1 - '@typescript-eslint/visitor-keys': 8.24.1 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/visitor-keys': 8.26.0 debug: 4.4.0 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -6460,17 +6463,17 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@8.24.1(eslint@9.20.1)(typescript@5.7.3): - resolution: {integrity: sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==} + /@typescript-eslint/utils@8.26.0(eslint@9.20.1)(typescript@5.7.3): + resolution: {integrity: sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.20.1) - '@typescript-eslint/scope-manager': 8.24.1 - '@typescript-eslint/types': 8.24.1 - '@typescript-eslint/typescript-estree': 8.24.1(typescript@5.7.3) + '@typescript-eslint/scope-manager': 8.26.0 + '@typescript-eslint/types': 8.26.0 + '@typescript-eslint/typescript-estree': 8.26.0(typescript@5.7.3) eslint: 9.20.1 typescript: 5.7.3 transitivePeerDependencies: @@ -6485,11 +6488,11 @@ packages: eslint-visitor-keys: 4.2.0 dev: true - /@typescript-eslint/visitor-keys@8.24.1: - resolution: {integrity: sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==} + /@typescript-eslint/visitor-keys@8.26.0: + resolution: {integrity: sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.24.1 + '@typescript-eslint/types': 8.26.0 eslint-visitor-keys: 4.2.0 dev: true @@ -12073,6 +12076,15 @@ packages: react: 18.3.1 scheduler: 0.23.2 + /react-error-boundary@5.0.0(react@18.3.1): + resolution: {integrity: sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==} + peerDependencies: + react: '>=16.13.1' + dependencies: + '@babel/runtime': 7.26.7 + react: 18.3.1 + dev: true + /react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} dev: false @@ -13394,6 +13406,11 @@ packages: engines: {node: '>=16'} dev: true + /type-fest@4.37.0: + resolution: {integrity: sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==} + engines: {node: '>=16'} + dev: true + /typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -13439,16 +13456,16 @@ packages: reflect.getprototypeof: 1.0.10 dev: true - /typescript-eslint@8.24.1(eslint@9.20.1)(typescript@5.7.3): - resolution: {integrity: sha512-cw3rEdzDqBs70TIcb0Gdzbt6h11BSs2pS0yaq7hDWDBtCCSei1pPSUXE9qUdQ/Wm9NgFg8mKtMt1b8fTHIl1jA==} + /typescript-eslint@8.26.0(eslint@9.20.1)(typescript@5.7.3): + resolution: {integrity: sha512-PtVz9nAnuNJuAVeUFvwztjuUgSnJInODAUx47VDwWPXzd5vismPOtPtt83tzNXyOjVQbPRp786D6WFW/M2koIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.8.0' + typescript: '>=4.8.4 <5.9.0' dependencies: - '@typescript-eslint/eslint-plugin': 8.24.1(@typescript-eslint/parser@8.24.1)(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/parser': 8.24.1(eslint@9.20.1)(typescript@5.7.3) - '@typescript-eslint/utils': 8.24.1(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/eslint-plugin': 8.26.0(@typescript-eslint/parser@8.26.0)(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/parser': 8.26.0(eslint@9.20.1)(typescript@5.7.3) + '@typescript-eslint/utils': 8.26.0(eslint@9.20.1)(typescript@5.7.3) eslint: 9.20.1 typescript: 5.7.3 transitivePeerDependencies: