diff --git a/packages/plugins/tanstack-query/package.json b/packages/plugins/tanstack-query/package.json index 96abe2896..fdf337d9b 100644 --- a/packages/plugins/tanstack-query/package.json +++ b/packages/plugins/tanstack-query/package.json @@ -58,6 +58,12 @@ "import": "./runtime-v5/svelte.mjs", "require": "./runtime-v5/svelte.js", "default": "./runtime-v5/svelte.js" + }, + "./runtime-v5/angular": { + "types": "./runtime-v5/angular.d.ts", + "import": "./runtime-v5/angular.mjs", + "require": "./runtime-v5/angular.js", + "default": "./runtime-v5/angular.js" } }, "repository": { @@ -88,6 +94,10 @@ "ts-pattern": "^4.3.0" }, "devDependencies": { + "@angular/core": "^20.0.0", + "@angular/common": "^20.0.0", + "@angular/platform-browser": "^20.0.0", + "@tanstack/angular-query-v5": "npm:@tanstack/angular-query-experimental@5.84.x", "@tanstack/react-query": "^4.29.7", "@tanstack/react-query-v5": "npm:@tanstack/react-query@5.56.x", "@tanstack/svelte-query": "^4.29.7", @@ -103,9 +113,12 @@ "nock": "^13.3.6", "react": "18.2.0", "react-test-renderer": "^18.2.0", + "rxjs": "^7.8.0", "svelte": "^4.2.1", "swr": "^2.0.3", "tmp": "^0.2.3", - "vue": "^3.3.4" + "typescript": "^5.0.0", + "vue": "^3.3.4", + "zone.js": "^0.15.0" } } diff --git a/packages/plugins/tanstack-query/scripts/postbuild.js b/packages/plugins/tanstack-query/scripts/postbuild.js index 9fbbace04..02abe2dfb 100755 --- a/packages/plugins/tanstack-query/scripts/postbuild.js +++ b/packages/plugins/tanstack-query/scripts/postbuild.js @@ -45,3 +45,10 @@ replaceSync({ from: /@tanstack\/vue-query-v5/g, to: '@tanstack/vue-query', }); + +console.log('Replacing @tanstack/angular-query-v5'); +replaceSync({ + file: 'dist/runtime-v5/angular*(.d.ts|.d.mts|.js|.mjs)', + from: /@tanstack\/angular-query-v5/g, + to: '@tanstack/angular-query-experimental', +}); diff --git a/packages/plugins/tanstack-query/src/generator.ts b/packages/plugins/tanstack-query/src/generator.ts index 31e5c235a..4f901da6c 100644 --- a/packages/plugins/tanstack-query/src/generator.ts +++ b/packages/plugins/tanstack-query/src/generator.ts @@ -21,7 +21,7 @@ import { Project, SourceFile, VariableDeclarationKind } from 'ts-morph'; import { P, match } from 'ts-pattern'; import { name } from '.'; -const supportedTargets = ['react', 'vue', 'svelte']; +const supportedTargets = ['react', 'vue', 'svelte', 'angular']; type TargetFramework = (typeof supportedTargets)[number]; type TanStackVersion = 'v4' | 'v5'; @@ -41,6 +41,11 @@ export async function generate(model: Model, options: PluginOptions, dmmf: DMMF. throw new PluginError(name, `Unsupported version "${version}": use "v4" or "v5"`); } + // Angular is only supported in v5 + if (target === 'angular' && version !== 'v5') { + throw new PluginError(name, `Angular target is only supported with version "v5", got "${version}"`); + } + let outDir = requireOption(options, 'output', name); outDir = resolvePath(outDir, options); ensureEmptyDir(outDir); @@ -224,6 +229,7 @@ function generateMutationHook( switch (target) { case 'react': case 'vue': + case 'angular': // override the mutateAsync function to return the correct type func.addVariableStatement({ declarationKind: VariableDeclarationKind.Const, @@ -597,6 +603,11 @@ function generateIndex( case 'svelte': sf.addStatements(`export { SvelteQueryContextKey, setHooksContext } from '${runtimeImportBase}/svelte';`); break; + case 'angular': + sf.addStatements( + `export { AngularQueryContextKey, provideAngularQueryContext } from '${runtimeImportBase}/angular';` + ); + break; } sf.addStatements(`export { default as metadata } from './__model_meta';`); } @@ -609,6 +620,8 @@ function makeGetContext(target: TargetFramework) { return 'const { endpoint, fetch } = getHooksContext();'; case 'svelte': return `const { endpoint, fetch } = getHooksContext();`; + case 'angular': + return 'const { endpoint, fetch } = getHooksContext();'; default: throw new PluginError(name, `Unsupported target "${target}"`); } @@ -658,6 +671,13 @@ function makeBaseImports(target: TargetFramework, version: TanStackVersion) { ...shared, ]; } + case 'angular': { + return [ + `import type { CreateMutationOptions, CreateQueryOptions, CreateInfiniteQueryOptions, InfiniteData } from '@tanstack/angular-query-v5';`, + `import { getHooksContext } from '${runtimeImportBase}/${target}';`, + ...shared, + ]; + } default: throw new PluginError(name, `Unsupported target: ${target}`); } @@ -707,6 +727,11 @@ function makeQueryOptions( ? `Omit, 'queryKey'>` : `StoreOrVal, 'queryKey'>>` ) + .with('angular', () => + infinite + ? `Omit>, 'queryKey' | 'initialPageParam'>` + : `Omit, 'queryKey'>` + ) .otherwise(() => { throw new PluginError(name, `Unsupported target: ${target}`); }); @@ -727,6 +752,7 @@ function makeMutationOptions(target: string, returnType: string, argsType: strin return `MaybeRefOrGetter<${baseOption}> | ComputedRef<${baseOption}>`; }) .with('svelte', () => `MutationOptions<${returnType}, DefaultError, ${argsType}>`) + .with('angular', () => `CreateMutationOptions<${returnType}, DefaultError, ${argsType}>`) .otherwise(() => { throw new PluginError(name, `Unsupported target: ${target}`); }); diff --git a/packages/plugins/tanstack-query/src/runtime-v5/angular.ts b/packages/plugins/tanstack-query/src/runtime-v5/angular.ts new file mode 100644 index 000000000..87f0881cd --- /dev/null +++ b/packages/plugins/tanstack-query/src/runtime-v5/angular.ts @@ -0,0 +1,207 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + injectQuery, + injectMutation, + injectInfiniteQuery, + QueryClient, + type CreateQueryOptions, + type CreateMutationOptions, + type CreateInfiniteQueryOptions, + type InfiniteData, + CreateInfiniteQueryResult, + QueryKey, +} from '@tanstack/angular-query-v5'; +import type { ModelMeta } from '@zenstackhq/runtime/cross'; +import { inject, InjectionToken } from '@angular/core'; +import { + APIContext, + DEFAULT_QUERY_ENDPOINT, + fetcher, + getQueryKey, + makeUrl, + marshal, + setupInvalidation, + setupOptimisticUpdate, + type ExtraMutationOptions, + type ExtraQueryOptions, + type FetchFn, +} from '../runtime/common'; + +export { APIContext as RequestHandlerContext } from '../runtime/common'; + +export const AngularQueryContextKey = new InjectionToken('zenstack-angular-query-context'); + +/** + * Provide context for the generated TanStack Query hooks. + */ +export function provideAngularQueryContext(context: APIContext) { + return { + provide: AngularQueryContextKey, + useValue: context, + }; +} + +/** + * Hooks context. + */ +export function getHooksContext() { + const context = inject(AngularQueryContextKey, { + optional: true, + }) || { + endpoint: DEFAULT_QUERY_ENDPOINT, + fetch: undefined, + logging: false, + }; + + const { endpoint, ...rest } = context; + return { endpoint: endpoint ?? DEFAULT_QUERY_ENDPOINT, ...rest }; +} + +/** + * Creates an Angular TanStack Query query. + * + * @param model The name of the model under query. + * @param url The request URL. + * @param args The request args object, URL-encoded and appended as "?q=" parameter + * @param options The Angular query options object + * @param fetch The fetch function to use for sending the HTTP request + * @returns injectQuery hook + */ +export function useModelQuery( + model: string, + url: string, + args?: unknown, + options?: Omit, 'queryKey'> & ExtraQueryOptions, + fetch?: FetchFn +) { + const reqUrl = makeUrl(url, args); + const queryKey = getQueryKey(model, url, args, { + infinite: false, + optimisticUpdate: options?.optimisticUpdate !== false, + }); + return { + queryKey, + ...injectQuery(() => ({ + queryKey, + queryFn: ({ signal }) => fetcher(reqUrl, { signal }, fetch, false), + ...options, + })), + }; +} + +/** + * Creates an Angular TanStack Query infinite query. + * + * @param model The name of the model under query. + * @param url The request URL. + * @param args The initial request args object, URL-encoded and appended as "?q=" parameter + * @param options The Angular infinite query options object + * @param fetch The fetch function to use for sending the HTTP request + * @returns injectInfiniteQuery hook + */ +export function useInfiniteModelQuery( + model: string, + url: string, + args: unknown, + options: Omit< + CreateInfiniteQueryOptions>, + 'queryKey' | 'initialPageParam' + >, + fetch?: FetchFn +): CreateInfiniteQueryResult, TError> & { queryKey: QueryKey } { + const queryKey = getQueryKey(model, url, args, { infinite: true, optimisticUpdate: false }); + return { + queryKey, + ...injectInfiniteQuery(() => ({ + queryKey, + queryFn: ({ pageParam, signal }) => { + return fetcher(makeUrl(url, pageParam ?? args), { signal }, fetch, false); + }, + initialPageParam: args, + ...options, + })), + }; +} + +/** + * Creates an Angular TanStack Query mutation. + * + * @param model The name of the model under mutation. + * @param method The HTTP method. + * @param url The request URL. + * @param modelMeta The model metadata. + * @param options The Angular mutation options. + * @param fetch The fetch function to use for sending the HTTP request + * @param checkReadBack Whether to check for read back errors and return undefined if found. + * @returns injectMutation hook + */ +export function useModelMutation< + TArgs, + TError, + R = any, + C extends boolean = boolean, + Result = C extends true ? R | undefined : R +>( + model: string, + method: 'POST' | 'PUT' | 'DELETE', + url: string, + modelMeta: ModelMeta, + options?: Omit, 'mutationFn'> & ExtraMutationOptions, + fetch?: FetchFn, + checkReadBack?: C +) { + const queryClient = inject(QueryClient); + const mutationFn = (data: unknown) => { + const reqUrl = method === 'DELETE' ? makeUrl(url, data) : url; + const fetchInit: RequestInit = { + method, + ...(method !== 'DELETE' && { + headers: { + 'content-type': 'application/json', + }, + body: marshal(data), + }), + }; + return fetcher(reqUrl, fetchInit, fetch, checkReadBack) as Promise; + }; + + const finalOptions = { ...options, mutationFn }; + const operation = url.split('/').pop(); + const invalidateQueries = options?.invalidateQueries !== false; + const optimisticUpdate = !!options?.optimisticUpdate; + + if (operation) { + const { logging } = getHooksContext(); + if (invalidateQueries) { + setupInvalidation( + model, + operation, + modelMeta, + finalOptions, + (predicate) => queryClient.invalidateQueries({ predicate }), + logging + ); + } + + if (optimisticUpdate) { + setupOptimisticUpdate( + model, + operation, + modelMeta, + finalOptions, + queryClient.getQueryCache().getAll(), + (queryKey, data) => { + // update query cache + queryClient.setQueryData(queryKey, data); + // cancel on-flight queries to avoid redundant cache updates, + // the settlement of the current mutation will trigger a new revalidation + queryClient.cancelQueries({ queryKey }, { revert: false, silent: true }); + }, + invalidateQueries ? (predicate) => queryClient.invalidateQueries({ predicate }) : undefined, + logging + ); + } + } + + return injectMutation(() => finalOptions); +} diff --git a/packages/plugins/tanstack-query/tests/plugin.test.ts b/packages/plugins/tanstack-query/tests/plugin.test.ts index c893c6bf7..9e9dd5fe9 100644 --- a/packages/plugins/tanstack-query/tests/plugin.test.ts +++ b/packages/plugins/tanstack-query/tests/plugin.test.ts @@ -291,6 +291,72 @@ ${sharedModel} ); }); + const angularAppSource = { + name: 'main.ts', + content: ` + import { Component, inject } from '@angular/core'; + import { useFindFirstpost_Item, useInfiniteFindManypost_Item, useCreatepost_Item } from './hooks'; + + @Component({ + selector: 'app-test', + template: '
Test Component
' + }) + export class TestComponent { + query() { + const { data, queryKey } = useFindFirstpost_Item({include: { author: true }}, { enabled: true, optimisticUpdate: false }); + console.log(queryKey); + console.log(data()?.viewCount); + console.log(data()?.author?.email); + } + + infiniteQuery() { + const { data, queryKey, fetchNextPage, hasNextPage } = useInfiniteFindManypost_Item(); + useInfiniteFindManypost_Item({ where: { published: true } }); + useInfiniteFindManypost_Item(undefined, { enabled: true, getNextPageParam: () => null }); + console.log(queryKey); + console.log(data()?.pages[0][0].published); + console.log(data()?.pageParams[0]); + } + + async mutation() { + const { mutateAsync } = useCreatepost_Item(); + const data = await mutateAsync({ data: { title: 'hello' }, include: { author: true } }); + console.log(data?.viewCount); + console.log(data?.author?.email); + } + } + `, + }; + + it('angular-query run plugin v5', async () => { + await loadSchema( + ` +plugin tanstack { + provider = '${normalizePath(path.resolve(__dirname, '../dist'))}' + output = '$projectRoot/hooks' + target = 'angular' +} + +${sharedModel} + `, + { + provider: 'postgresql', + pushDb: false, + extraDependencies: [ + '@angular/core@^20.0.0', + '@angular/common@^20.0.0', + '@angular/platform-browser@^20.0.0', + '@tanstack/angular-query-v5@npm:@tanstack/angular-query-experimental@5.84.x', + 'rxjs@^7.8.0', + 'zone.js@^0.15.0' + ], + copyDependencies: [path.resolve(__dirname, '../dist')], + compile: true, + extraSourceFiles: [angularAppSource], + } + ); + }); + it('clear output', async () => { const { name: projectDir } = tmp.dirSync(); fs.mkdirSync(path.join(projectDir, 'tanstack'), { recursive: true }); diff --git a/packages/plugins/tanstack-query/tsup-v5.config.ts b/packages/plugins/tanstack-query/tsup-v5.config.ts index d619109b7..145f56a5c 100644 --- a/packages/plugins/tanstack-query/tsup-v5.config.ts +++ b/packages/plugins/tanstack-query/tsup-v5.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - entry: ['src/runtime-v5/index.ts', 'src/runtime-v5/react.ts', 'src/runtime-v5/vue.ts', 'src/runtime-v5/svelte.ts'], + entry: ['src/runtime-v5/index.ts', 'src/runtime-v5/react.ts', 'src/runtime-v5/vue.ts', 'src/runtime-v5/svelte.ts', 'src/runtime-v5/angular.ts'], outDir: 'dist/runtime-v5', splitting: false, sourcemap: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b2add801..f3e40be76 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -256,6 +256,18 @@ importers: specifier: ^4.3.0 version: 4.3.0 devDependencies: + '@angular/common': + specifier: ^20.0.0 + version: 20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1) + '@angular/core': + specifier: ^20.0.0 + version: 20.1.4(rxjs@7.8.1)(zone.js@0.15.1) + '@angular/platform-browser': + specifier: ^20.0.0 + version: 20.1.4(@angular/common@20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1)) + '@tanstack/angular-query-v5': + specifier: npm:@tanstack/angular-query-experimental@5.84.x + version: '@tanstack/angular-query-experimental@5.84.1(@angular/common@20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))' '@tanstack/react-query': specifier: ^4.29.7 version: 4.36.1(react-dom@18.3.1(react@18.2.0))(react@18.2.0) @@ -301,6 +313,9 @@ importers: react-test-renderer: specifier: ^18.2.0 version: 18.3.1(react@18.2.0) + rxjs: + specifier: ^7.8.0 + version: 7.8.1 svelte: specifier: ^4.2.1 version: 4.2.18 @@ -310,9 +325,15 @@ importers: tmp: specifier: ^0.2.3 version: 0.2.3 + typescript: + specifier: ^5.0.0 + version: 5.9.2 vue: specifier: ^3.3.4 version: 3.4.31(typescript@5.9.2) + zone.js: + specifier: ^0.15.0 + version: 0.15.1 publishDirectory: dist packages/plugins/trpc: @@ -806,6 +827,37 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@angular/common@20.1.4': + resolution: {integrity: sha512-AL+HdsY5xL2iM1zZ55ce33U+w2LgPJZQwKvHXJJ/Hpk3rpFNamWtRPmJBeq8Z0dQV1lLTMM+2pUatH6p+5pvEg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/core': 20.1.4 + rxjs: ^6.5.3 || ^7.4.0 + + '@angular/core@20.1.4': + resolution: {integrity: sha512-aWDux64a9usuVU2SnF0epqjXAj8JO8jViUzZAJAuFKSCtkeNzqP+Z6DjkqsCKrNvGP7xkX1XhhepUygxgh7/6A==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/compiler': 20.1.4 + rxjs: ^6.5.3 || ^7.4.0 + zone.js: ~0.15.0 + peerDependenciesMeta: + '@angular/compiler': + optional: true + zone.js: + optional: true + + '@angular/platform-browser@20.1.4': + resolution: {integrity: sha512-z86NsGSwm5pXCACdWBbp7SC1Xn+UGvuoRqTsi0dNUXT/3WrP6MvZT3TfNKwM63GLUqFAICSt7uFXS84D72ukvA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/animations': 20.1.4 + '@angular/common': 20.1.4 + '@angular/core': 20.1.4 + peerDependenciesMeta: + '@angular/animations': + optional: true + '@antfu/utils@0.7.10': resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} @@ -2872,6 +2924,12 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@tanstack/angular-query-experimental@5.84.1': + resolution: {integrity: sha512-YQBCD6J29kyju5xAnomHjPYsz9Q/ARvIUIN0xDkpxuhhc/clSWokgj0hlCLYZP8Pj15sxgDn0Lkc/rH7hfsFlQ==} + peerDependencies: + '@angular/common': '>=16.0.0' + '@angular/core': '>=16.0.0' + '@tanstack/match-sorter-utils@8.15.1': resolution: {integrity: sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw==} engines: {node: '>=12'} @@ -2885,6 +2943,12 @@ packages: '@tanstack/query-core@5.56.2': resolution: {integrity: sha512-gor0RI3/R5rVV3gXfddh1MM+hgl0Z4G7tj6Xxpq6p2I03NGPaJ8dITY9Gz05zYYb/EJq9vPas/T4wn9EaDPd4Q==} + '@tanstack/query-core@5.83.1': + resolution: {integrity: sha512-OG69LQgT7jSp+5pPuCfzltq/+7l2xoweggjme9vlbCPa/d7D7zaqv5vN/S82SzSYZ4EDLTxNO1PWrv49RAS64Q==} + + '@tanstack/query-devtools@5.84.0': + resolution: {integrity: sha512-fbF3n+z1rqhvd9EoGp5knHkv3p5B2Zml1yNRjh7sNXklngYI5RVIWUrUjZ1RIcEoscarUb0+bOvIs5x9dwzOXQ==} + '@tanstack/react-query@4.36.1': resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} peerDependencies: @@ -7534,6 +7598,7 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions spawn-command@0.0.2-1: resolution: {integrity: sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==} @@ -8755,6 +8820,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zone.js@0.15.1: + resolution: {integrity: sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==} + snapshots: '@ampproject/remapping@2.3.0': @@ -8762,6 +8830,25 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@angular/common@20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1)': + dependencies: + '@angular/core': 20.1.4(rxjs@7.8.1)(zone.js@0.15.1) + rxjs: 7.8.1 + tslib: 2.6.3 + + '@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1)': + dependencies: + rxjs: 7.8.1 + tslib: 2.6.3 + optionalDependencies: + zone.js: 0.15.1 + + '@angular/platform-browser@20.1.4(@angular/common@20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))': + dependencies: + '@angular/common': 20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1) + '@angular/core': 20.1.4(rxjs@7.8.1)(zone.js@0.15.1) + tslib: 2.6.3 + '@antfu/utils@0.7.10': {} '@apidevtools/swagger-methods@3.0.2': {} @@ -11087,6 +11174,13 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.6.3 + '@tanstack/angular-query-experimental@5.84.1(@angular/common@20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1))(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))': + dependencies: + '@angular/common': 20.1.4(@angular/core@20.1.4(rxjs@7.8.1)(zone.js@0.15.1))(rxjs@7.8.1) + '@angular/core': 20.1.4(rxjs@7.8.1)(zone.js@0.15.1) + '@tanstack/query-core': 5.83.1 + '@tanstack/query-devtools': 5.84.0 + '@tanstack/match-sorter-utils@8.15.1': dependencies: remove-accents: 0.5.0 @@ -11097,6 +11191,10 @@ snapshots: '@tanstack/query-core@5.56.2': {} + '@tanstack/query-core@5.83.1': {} + + '@tanstack/query-devtools@5.84.0': {} + '@tanstack/react-query@4.36.1(react-dom@18.3.1(react@18.2.0))(react@18.2.0)': dependencies: '@tanstack/query-core': 4.36.1 @@ -17903,3 +18001,5 @@ snapshots: zod: 3.25.76 zod@3.25.76: {} + + zone.js@0.15.1: {}