From e9691164b9f321cc9f34c971bba66fc9e7a8f9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Tue, 12 Nov 2024 20:50:29 +0100 Subject: [PATCH 01/61] fix error with hot module replacement --- .../src/useCachedResponsivePrecommitValue.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts index cbe3c86f2..ee5460a5f 100644 --- a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts +++ b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts @@ -51,6 +51,10 @@ export function useCachedResponsivePrecommitValue( const lastCommittedParentCache = useRef | null>(null); useEffect(() => { + if (lastCommittedParentCache.current === parentCache) { + return; + } + lastCommittedParentCache.current = parentCache; // On commit, cacheItem may be disposed, because during the render phase, // we only temporarily retained the item, and the temporary retain could have From 25ba84c389ede92ca597fa256283fefe3aa39781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Tue, 12 Nov 2024 21:18:15 +0100 Subject: [PATCH 02/61] update tests --- .../isograph-react-disposable-state/package.json | 1 + .../useCachedResponsivePrecommitValue.test.tsx | 7 ++++--- pnpm-lock.yaml | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/libs/isograph-react-disposable-state/package.json b/libs/isograph-react-disposable-state/package.json index 865cbb8c0..c0d5f819c 100644 --- a/libs/isograph-react-disposable-state/package.json +++ b/libs/isograph-react-disposable-state/package.json @@ -25,6 +25,7 @@ }, "devDependencies": { "@types/react": "18.3.1", + "@types/react-test-renderer": "^18.3.0", "react-test-renderer": "^18.2.0", "typescript": "5.6.3" }, diff --git a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx index f743da216..d8415b931 100644 --- a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx +++ b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.test.tsx @@ -2,7 +2,7 @@ import { describe, test, vi, expect, assert } from 'vitest'; import { ParentCache } from './ParentCache'; import { ItemCleanupPair } from '@isograph/disposable-types'; import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue'; -import React from 'react'; +import React, { StrictMode, type ReactElement } from 'react'; import { create } from 'react-test-renderer'; import { CacheItem, CacheItemState } from './CacheItem'; @@ -51,9 +51,10 @@ function promiseAndResolver() { // The fact that sometimes we need to render in concurrent mode and sometimes // not is a bit worrisome. -async function awaitableCreate(Component, isConcurrent) { +async function awaitableCreate(Component: ReactElement, isConcurrent) { const element = create( - Component, + {Component}, + // @ts-expect-error isConcurrent ? { unstable_isConcurrent: true } : undefined, ); await shortPromise(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 234c80957..98e0178d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -364,6 +364,9 @@ importers: '@types/react': specifier: 18.3.1 version: 18.3.1 + '@types/react-test-renderer': + specifier: ^18.3.0 + version: 18.3.0 react-test-renderer: specifier: ^18.2.0 version: 18.3.1(react@18.3.1) @@ -2267,6 +2270,9 @@ packages: '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} + '@types/react-test-renderer@18.3.0': + resolution: {integrity: sha512-HW4MuEYxfDbOHQsVlY/XtOvNHftCVEPhJF2pQXXwcUiUF+Oyb0usgp48HSgpK5rt8m9KZb22yqOeZm+rrVG8gw==} + '@types/react-transition-group@4.4.11': resolution: {integrity: sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==} @@ -10280,6 +10286,10 @@ snapshots: '@types/history': 4.7.11 '@types/react': 18.3.1 + '@types/react-test-renderer@18.3.0': + dependencies: + '@types/react': 18.3.1 + '@types/react-transition-group@4.4.11': dependencies: '@types/react': 18.3.1 @@ -12087,7 +12097,7 @@ snapshots: eslint: 8.39.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0))(eslint@8.39.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.39.0) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.39.0) eslint-plugin-react: 7.36.1(eslint@8.39.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.39.0) @@ -12137,7 +12147,7 @@ snapshots: is-bun-module: 1.2.1 is-glob: 4.0.3 optionalDependencies: - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0))(eslint@8.39.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.39.0) transitivePeerDependencies: - '@typescript-eslint/parser' - eslint-import-resolver-node @@ -12185,7 +12195,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(eslint@8.39.0))(eslint@8.39.0))(eslint@8.39.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@5.62.0(eslint@8.39.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.39.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 From 7d4628dcf10476c39658f956fa957f2abe18ddf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:07:00 +0100 Subject: [PATCH 03/61] fix cleanup --- .../src/useDisposableState.ts | 24 ++++++++++++------- .../src/useLazyDisposableState.test.tsx | 9 +++++-- .../src/useLazyDisposableState.ts | 14 +++++++---- .../src/useUpdatableDisposableState.test.tsx | 5 ++-- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/libs/isograph-react-disposable-state/src/useDisposableState.ts b/libs/isograph-react-disposable-state/src/useDisposableState.ts index 46a63fe81..81e18fffe 100644 --- a/libs/isograph-react-disposable-state/src/useDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useDisposableState.ts @@ -22,7 +22,6 @@ export function useDisposableState( const preCommitItem = useCachedResponsivePrecommitValue( parentCache, (pair) => { - itemCleanupPairRef.current?.[1](); itemCleanupPairRef.current = pair; }, ); @@ -47,14 +46,23 @@ export function useDisposableState( [stateFromDisposableStateHook], ); - useEffect(function cleanupItemCleanupPairRefIfSetStateNotCalled() { - return () => { - if (itemCleanupPairRef.current !== null) { - itemCleanupPairRef.current[1](); - itemCleanupPairRef.current = null; + const lastCommittedParentCache = useRef | null>(null); + + useEffect( + function cleanupItemCleanupPairRefIfSetStateNotCalled() { + if (lastCommittedParentCache.current === parentCache) { + return; } - }; - }, []); + lastCommittedParentCache.current = parentCache; + // capture last set pair in a variable + const current = itemCleanupPairRef.current; + return () => { + // current is a stale variable + current?.[1](); + }; + }, + [parentCache], + ); // Safety: we can be in one of three states. Pre-commit, in which case // preCommitItem is assigned, post-commit but before setState has been diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx b/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx index 9d206cd63..38f4c3679 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.test.tsx @@ -1,5 +1,5 @@ import { ItemCleanupPair } from '@isograph/disposable-types'; -import React, { useEffect, useState } from 'react'; +import React, { StrictMode, useEffect, useState } from 'react'; import { create } from 'react-test-renderer'; import { describe, expect, test, vi } from 'vitest'; import { ParentCache } from './ParentCache'; @@ -55,7 +55,12 @@ describe('useLazyDisposableState', async () => { return null; } - const root = create(, { unstable_isConcurrent: true }); + const root = create( + + + , + { unstable_isConcurrent: true }, + ); await committed.promise; expect(cache1.disposeItem).toHaveBeenCalled(); expect(cache1.cache.factory).toHaveBeenCalledOnce(); diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts index 5c251fd32..38da37c4c 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts @@ -23,18 +23,24 @@ export function useLazyDisposableState( state: T; } { const itemCleanupPairRef = useRef | null>(null); - const preCommitItem = useCachedResponsivePrecommitValue( parentCache, (pair) => { - itemCleanupPairRef.current?.[1](); itemCleanupPairRef.current = pair; }, ); + const lastCommittedParentCache = useRef | null>(null); useEffect(() => { + if (lastCommittedParentCache.current === parentCache) { + return; + } + lastCommittedParentCache.current = parentCache; + // capture last set pair in a variable + const current = itemCleanupPairRef.current; return () => { - const cleanupFn = itemCleanupPairRef.current?.[1]; + // current is a stale variable + const cleanupFn = current?.[1]; // TODO confirm useEffect is called in order. if (cleanupFn == null) { throw new Error( @@ -43,7 +49,7 @@ export function useLazyDisposableState( } return cleanupFn(); }; - }, []); + }, [parentCache]); const returnedItem = preCommitItem?.state ?? itemCleanupPairRef.current?.[0]; diff --git a/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx b/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx index e74c132c8..48459175e 100644 --- a/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx +++ b/libs/isograph-react-disposable-state/src/useUpdatableDisposableState.test.tsx @@ -1,5 +1,5 @@ import { describe, test, vi, expect } from 'vitest'; -import React from 'react'; +import React, { StrictMode } from 'react'; import { create } from 'react-test-renderer'; import { useUpdatableDisposableState, @@ -45,7 +45,8 @@ function promiseAndResolver() { // not is a bit worrisome. async function awaitableCreate(Component, isConcurrent) { const element = create( - Component, + {Component}, + isConcurrent ? { unstable_isConcurrent: true } : undefined, ); await shortPromise(); From 1ddc73b041c26543e2d6abe467a7eb42ed782d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:26:30 +0100 Subject: [PATCH 04/61] don't use stale itemCleanupPairRef --- .../src/useDisposableState.ts | 8 ++++---- .../src/useLazyDisposableState.ts | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/libs/isograph-react-disposable-state/src/useDisposableState.ts b/libs/isograph-react-disposable-state/src/useDisposableState.ts index 81e18fffe..d5e979459 100644 --- a/libs/isograph-react-disposable-state/src/useDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useDisposableState.ts @@ -54,11 +54,11 @@ export function useDisposableState( return; } lastCommittedParentCache.current = parentCache; - // capture last set pair in a variable - const current = itemCleanupPairRef.current; + return () => { - // current is a stale variable - current?.[1](); + if (itemCleanupPairRef.current !== null) { + itemCleanupPairRef.current[1](); + } }; }, [parentCache], diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts index 38da37c4c..42a857e65 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts @@ -36,11 +36,9 @@ export function useLazyDisposableState( return; } lastCommittedParentCache.current = parentCache; - // capture last set pair in a variable - const current = itemCleanupPairRef.current; + return () => { - // current is a stale variable - const cleanupFn = current?.[1]; + const cleanupFn = itemCleanupPairRef.current?.[1]; // TODO confirm useEffect is called in order. if (cleanupFn == null) { throw new Error( From bdd06db292f6727ca00cba376e5436937acc6a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:42:13 +0100 Subject: [PATCH 05/61] add comment for guards added in `useEffects` --- .../src/useCachedResponsivePrecommitValue.ts | 4 ++++ .../src/useLazyDisposableState.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts index ee5460a5f..21c2c9c9c 100644 --- a/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts +++ b/libs/isograph-react-disposable-state/src/useCachedResponsivePrecommitValue.ts @@ -51,6 +51,10 @@ export function useCachedResponsivePrecommitValue( const lastCommittedParentCache = useRef | null>(null); useEffect(() => { + // react reruns all `useEffect` in HMR since it doesn't know if the + // code inside of useEffect has changed. Since this is a library + // user can't change this code so we are safe to skip this rerun. + // This also prevents `useEffect` from running twice in Strict Mode. if (lastCommittedParentCache.current === parentCache) { return; } diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts index 42a857e65..930752c36 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts @@ -32,6 +32,10 @@ export function useLazyDisposableState( const lastCommittedParentCache = useRef | null>(null); useEffect(() => { + // react reruns all `useEffect` in HMR since it doesn't know if the + // code inside of useEffect has changed. Since this is a library + // user can't change this code so we are safe to skip this rerun. + // This also prevents `useEffect` from running twice in Strict Mode. if (lastCommittedParentCache.current === parentCache) { return; } From cefeafa63f1da3898ec454c9defc139c731b9586 Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Wed, 13 Nov 2024 10:32:51 -0800 Subject: [PATCH 06/61] chore: Fix vite-demo type checking (#265) What - Fixes the vite-demo tsc command How - Added the -b flag. The tsc error was about an output file not matching an input file, so by building, we ensure everything is generated so the error doesn't pop up again. I don't pretend to entirely understand exactly why, but it does work and will check types on the demo! --- demos/vite-demo/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/vite-demo/package.json b/demos/vite-demo/package.json index bc03c62fd..75401235c 100644 --- a/demos/vite-demo/package.json +++ b/demos/vite-demo/package.json @@ -8,7 +8,7 @@ "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview", - "tsc": "echo TODO figure out why this fails in CI", + "tsc": "tsc -b", "iso": "../../target/debug/isograph_cli --config ./isograph.config.json", "iso-watch": "../../target/debug/isograph_cli --config ./isograph.config.json --watch" }, From c0bc4997083590fc7348bd704d97d67d86f409b7 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 13 Nov 2024 21:50:23 -0500 Subject: [PATCH 07/61] update docs and demo slightly (#275) - document `useImperativeReference` and mutations - modify the pet demo to show the result of a mutation --- .../src/components/PetTaglineCard.tsx | 31 ++-- .../Mutation/SetTagline/output_type.ts | 3 +- .../Mutation/SetTagline/resolver_reader.ts | 10 +- .../pet-demo/src/components/__isograph/iso.ts | 2 +- docs-website/docs/mutation.md | 135 ++++++++++++++++++ docs-website/docs/pagination.md | 4 +- docs-website/docs/quickstart.md | 2 +- docs-website/sidebars.ts | 3 +- .../isograph-react/src/core/componentCache.ts | 10 +- 9 files changed, 174 insertions(+), 26 deletions(-) create mode 100644 docs-website/docs/mutation.md diff --git a/demos/pet-demo/src/components/PetTaglineCard.tsx b/demos/pet-demo/src/components/PetTaglineCard.tsx index bc5bd297d..4c83018b1 100644 --- a/demos/pet-demo/src/components/PetTaglineCard.tsx +++ b/demos/pet-demo/src/components/PetTaglineCard.tsx @@ -1,14 +1,14 @@ -import React from 'react'; +import React, { Suspense } from 'react'; import { iso } from '@iso'; import { Button, Card, CardContent } from '@mui/material'; -import { useImperativeReference } from '@isograph/react'; +import { FragmentReader, useImperativeReference } from '@isograph/react'; import { UNASSIGNED_STATE } from '@isograph/react-disposable-state'; export const PetTaglineCard = iso(` -field Pet.PetTaglineCard @component { - id - tagline -} + field Pet.PetTaglineCard @component { + id + tagline + } `)(function PetTaglineCardComponent({ data: pet }) { const { fragmentReference: mutationRef, @@ -23,7 +23,7 @@ field Pet.PetTaglineCard @component {

Tagline

"{pet.tagline}"

- {mutationRef == UNASSIGNED_STATE ? ( + {mutationRef === UNASSIGNED_STATE ? ( - ) : null} + ) : ( + + + + )}
); }); export const setTagline = iso(` - field Mutation.SetTagline($input: SetPetTaglineParams!) { + field Mutation.SetTagline($input: SetPetTaglineParams!) @component { set_pet_tagline(input: $input) { pet { tagline } } } -`)(() => {}); +`)(({ data }) => { + return ( + <> + Nice! You updated the pet's tagline to{' '} + {data.set_pet_tagline?.pet?.tagline}! + + ); +}); diff --git a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts index 8da885229..ccd5908c6 100644 --- a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts +++ b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/output_type.ts @@ -1,3 +1,4 @@ +import type { ExtractSecondParam, CombineWithIntrinsicAttributes } from '@isograph/react'; import type React from 'react'; import { setTagline as resolver } from '../../../PetTaglineCard'; -export type Mutation__SetTagline__output_type = ReturnType; \ No newline at end of file +export type Mutation__SetTagline__output_type = (React.FC>>); diff --git a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts index 6da5e1ce0..4e9d526a6 100644 --- a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/resolver_reader.ts @@ -1,6 +1,5 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type {ComponentReaderArtifact, ExtractSecondParam, ReaderAst } from '@isograph/react'; import { Mutation__SetTagline__param } from './param_type'; -import { Mutation__SetTagline__output_type } from './output_type'; import { setTagline as resolver } from '../../../PetTaglineCard'; const readerAst: ReaderAst = [ @@ -35,11 +34,12 @@ const readerAst: ReaderAst = [ }, ]; -const artifact: EagerReaderArtifact< +const artifact: ComponentReaderArtifact< Mutation__SetTagline__param, - Mutation__SetTagline__output_type + ExtractSecondParam > = { - kind: "EagerReaderArtifact", + kind: "ComponentReaderArtifact", + componentName: "Mutation.SetTagline", resolver, readerAst, }; diff --git a/demos/pet-demo/src/components/__isograph/iso.ts b/demos/pet-demo/src/components/__isograph/iso.ts index 5b533bd49..d49365be6 100644 --- a/demos/pet-demo/src/components/__isograph/iso.ts +++ b/demos/pet-demo/src/components/__isograph/iso.ts @@ -116,7 +116,7 @@ export function iso( export function iso( param: T & MatchesWhitespaceAndString<'field Mutation.SetTagline', T> -): IdentityWithParam; +): IdentityWithParamComponent; export function iso( param: T & MatchesWhitespaceAndString<'field NewsfeedItem.NewsfeedAdOrBlog', T> diff --git a/docs-website/docs/mutation.md b/docs-website/docs/mutation.md new file mode 100644 index 000000000..5ba8acf74 --- /dev/null +++ b/docs-website/docs/mutation.md @@ -0,0 +1,135 @@ +# Mutations + +In Isograph, mutations aren't special. The distinguishing feature of mutations is often that you want to make the network request at a specific time, for example, in response to a user clicking a button. This document describes how to make network requests in response to events, which you can use to trigger a mutation. Okay, onward! + +## Walkthrough + +### Defining the mutation field + +First, define a client field on the `Mutation` object that calls the mutation you care about: + +```js +export const setTagline = iso(` + field Mutation.SetTagline($input: SetPetTaglineParams!) { + set_pet_tagline(input: $input) { + pet { + tagline + } + } + } +`)((({data})) => data); +``` + +Make sure you select the fields that you want refetched and written into the store. In this case, we want to see the updated pet's tagline. We also return the data. + +:::note +Note also that we're naming the field `Mutation.SetTagline`, _not_ `Mutation.set_pet_tagline`. There already is a field named `Mutation.set_pet_tagline`, defined in the schema. So, if you attempt to define a client field named `Mutation.set_pet_tagline`, the Isograph compiler will emit an error and refuse to compile! +::: + +### Calling the mutation + +Next, call `useImperativeReference`. This gives you back a fragment reference and a `loadFragmentReference` function: + +```jsx +import { useImperativeReference } from '@isograph/react'; +import { UNASSIGNED_STATE } from '@isograph/react-disposable-state'; + +export const PetTaglineCard = iso(` + field Pet.PetTaglineCard @component { + id + tagline + } +`)(function PetTaglineCardComponent({ data: pet }) { + const { + fragmentReference: mutationRef, + loadFragmentReference: loadMutation, + } = useImperativeReference(iso(`entrypoint Mutation.SetTagline`)); + // ... +} +``` + +Next, call `loadFragmentReference` (`loadMutation` in this example) when a user clicks! + +```jsx +{ + mutationRef === UNASSIGNED_STATE ? ( + + ) : null; +} +``` + +Since we only want to set the tagline once, we check that `mutation === UNASSIGNED_STATE` before showing the button. + +### Reading the results + +What about reading the results of the mutation? There are two good ways to do this, and two additional ways that will work in the future. + +First, we can wait until the network response completes and see the component re-render with the updated tagline. + +For the second method, we can modify the mutation field as follows: + +```js +export const setTagline = iso(` + field Mutation.SetTagline($input: SetPetTaglineParams!) @component { + set_pet_tagline(input: $input) { + pet { + tagline + } + } + } +`)((({data})) => { + return ( + <> + Nice! You updated the pet's tagline to{' '} + {data.set_pet_tagline?.pet?.tagline}! + + ); +}); +``` + +Here, we add the `@component` annotation and return some JSX. + +Now, we can use this! Modify the `PetTaglineCardComponent` component as follows: + +```js +{ + mutationRef === UNASSIGNED_STATE ? ( + + ) : ( + + + + ); +} +``` + +#### Additional methods + +The following methods will be available in the future: + +- When reading a fragment reference is not a hook (see [this issue](https://github.com/isographlabs/isograph/issues/273)), you should be able to read the fragment conditionally in the parent component. +- `useImperativeReference` (et al) will receive a `onCompleted` parameter that will be executed when the mutation completes, and will be passed the mutation results. You can set this in state. This is probably a bad practice, and shouldn't be relied on. diff --git a/docs-website/docs/pagination.md b/docs-website/docs/pagination.md index 7270a8ccb..9a0b254cc 100644 --- a/docs-website/docs/pagination.md +++ b/docs-website/docs/pagination.md @@ -8,7 +8,7 @@ This API is likely to get simplified substantially, as we support `@loadable` on ## Walk through -Define a client field (without `@component`) that returns an array of items and accepts `skip` and `limit` parameters. It can accept other params: +First, define a client field (without `@component`) that returns an array of items and accepts `skip` and `limit` parameters. It can accept other params: ```tsx import { iso } from '@iso'; @@ -77,3 +77,5 @@ export const PetDetailDeferredRouteComponent = iso(` ); }); ``` + +You can also use `useConnectionSpecPagination` if your connection field conforms to the [Relay connection spec](https://facebook.github.io/relay/graphql/connections.htm). diff --git a/docs-website/docs/quickstart.md b/docs-website/docs/quickstart.md index f69b88b0e..9a8e4b329 100644 --- a/docs-website/docs/quickstart.md +++ b/docs-website/docs/quickstart.md @@ -462,6 +462,6 @@ Now, if you refresh, the UI will be divided into subcomponents and look exactly Congratulations! You just built your first Isograph app. -Want more? Try extracting the sorted list of films into its own client field (no need to use `@component` for this one.) Use hooks in your components (they work!) Check out the [magic mutation fields](/docs/expose-field-directives/) documentation to learn about how Isograph lets you update your data. +Want more? Try extracting the sorted list of films into its own client field (no need to use `@component` for this one.) Use hooks in your components (they work!) Check out the [mutation](/docs/mutation/) documentation to learn about how Isograph lets you update your data. Or, [join the Discord](https://discord.gg/qcHUxb6deQ)! diff --git a/docs-website/sidebars.ts b/docs-website/sidebars.ts index 962853fe3..c887ca49b 100644 --- a/docs-website/sidebars.ts +++ b/docs-website/sidebars.ts @@ -18,8 +18,9 @@ const sidebars: SidebarsConfig = { 'isograph-config', 'loadable-fields', 'pagination', - 'refetching', + 'mutation', 'expose-field-directives', + 'refetching', 'isograph-rules', 'faq', { diff --git a/libs/isograph-react/src/core/componentCache.ts b/libs/isograph-react/src/core/componentCache.ts index 8cf6ed2d1..754feafd3 100644 --- a/libs/isograph-react/src/core/componentCache.ts +++ b/libs/isograph-react/src/core/componentCache.ts @@ -47,13 +47,11 @@ export function getOrCreateCachedComponent( rootLink: fragmentReference.root, }); - const firstParameter = { - data, - parameters: fragmentReference.variables, - }; - return readerWithRefetchQueries.readerArtifact.resolver( - firstParameter, + { + data, + parameters: fragmentReference.variables, + }, additionalRuntimeProps, ); } From 8389393ede8133871abe9550b73e2b92807e3a1c Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 13 Nov 2024 21:55:25 -0500 Subject: [PATCH 08/61] no need to return data in example --- docs-website/docs/mutation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-website/docs/mutation.md b/docs-website/docs/mutation.md index 5ba8acf74..301c6ba69 100644 --- a/docs-website/docs/mutation.md +++ b/docs-website/docs/mutation.md @@ -17,7 +17,7 @@ export const setTagline = iso(` } } } -`)((({data})) => data); +`)((()) => {}); ``` Make sure you select the fields that you want refetched and written into the store. In this case, we want to see the updated pet's tagline. We also return the data. From 4df52ad684edab3e1eccfdb31d1a2856648c853b Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 13 Nov 2024 21:58:37 -0500 Subject: [PATCH 09/61] update docs some more --- docs-website/docs/mutation.md | 4 ++++ docs-website/docs/pagination.md | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs-website/docs/mutation.md b/docs-website/docs/mutation.md index 301c6ba69..fecc7e6fe 100644 --- a/docs-website/docs/mutation.md +++ b/docs-website/docs/mutation.md @@ -2,6 +2,10 @@ In Isograph, mutations aren't special. The distinguishing feature of mutations is often that you want to make the network request at a specific time, for example, in response to a user clicking a button. This document describes how to make network requests in response to events, which you can use to trigger a mutation. Okay, onward! +:::note +Examples in this document are taken from [this file](https://github.com/isographlabs/isograph/blob/91d3020f7f28a9cd91c250a9457f6a6bc7fd1562/demos/pet-demo/src/components/PetTaglineCard.tsx). +::: + ## Walkthrough ### Defining the mutation field diff --git a/docs-website/docs/pagination.md b/docs-website/docs/pagination.md index 9a0b254cc..f77ca4c63 100644 --- a/docs-website/docs/pagination.md +++ b/docs-website/docs/pagination.md @@ -3,7 +3,11 @@ Loadable fields can also be used to paginate. :::note -This API is likely to get simplified substantially, as we support `@loadable` on linked server fields. We will also add support for Relay-style pagination. +This API is likely to get simplified substantially, as we support `@loadable` on linked server fields. +::: + +:::note +See [this file](https://github.com/isographlabs/isograph/blob/dc7beaeab163159a9b38dbe3cbd731f7a03b3e38/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx) for a real-world example. ::: ## Walk through From 8a2737c39271a566c41d592b361ea1bae30802c0 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 13 Nov 2024 22:55:49 -0500 Subject: [PATCH 10/61] FetchOptions now have optional `onCompleted` and `onError` callbacks. Both are called with no parameters. Update docs accordingly. --- .../src/components/FavoritePhrase.tsx | 16 ++++++- .../src/components/PetDetailRoute.tsx | 8 ++++ docs-website/docs/mutation.md | 45 ++++++++++++++++++- libs/isograph-react/src/core/cache.ts | 5 +-- libs/isograph-react/src/core/check.ts | 2 + .../src/core/makeNetworkRequest.ts | 29 +++++++++--- libs/isograph-react/src/core/read.ts | 11 ++--- .../src/react/useImperativeReference.ts | 6 +-- 8 files changed, 99 insertions(+), 23 deletions(-) diff --git a/demos/pet-demo/src/components/FavoritePhrase.tsx b/demos/pet-demo/src/components/FavoritePhrase.tsx index 217c955a9..0e005c86e 100644 --- a/demos/pet-demo/src/components/FavoritePhrase.tsx +++ b/demos/pet-demo/src/components/FavoritePhrase.tsx @@ -16,7 +16,21 @@ export const FavoritePhraseLoader = iso(` return ( <> {fragmentReference == UNASSIGNED_STATE ? ( - ) : ( diff --git a/demos/pet-demo/src/components/PetDetailRoute.tsx b/demos/pet-demo/src/components/PetDetailRoute.tsx index 62892da75..1e4af6715 100644 --- a/demos/pet-demo/src/components/PetDetailRoute.tsx +++ b/demos/pet-demo/src/components/PetDetailRoute.tsx @@ -53,6 +53,14 @@ export function PetDetailRouteLoader({ route }: { route: PetDetailRoute }) { const { fragmentReference } = useLazyReference( iso(`entrypoint Query.PetDetailRoute`), { id: route.id }, + { + onComplete: () => { + console.log('The Query.PetDetailRoute network request has completed.'); + }, + onError: () => { + console.log('The Query.PetDetailRoute network request errored out.'); + }, + }, ); return ( diff --git a/docs-website/docs/mutation.md b/docs-website/docs/mutation.md index fecc7e6fe..21a264dc7 100644 --- a/docs-website/docs/mutation.md +++ b/docs-website/docs/mutation.md @@ -131,9 +131,50 @@ Now, we can use this! Modify the `PetTaglineCardComponent` component as follows: } ``` -#### Additional methods +#### Future methods The following methods will be available in the future: - When reading a fragment reference is not a hook (see [this issue](https://github.com/isographlabs/isograph/issues/273)), you should be able to read the fragment conditionally in the parent component. -- `useImperativeReference` (et al) will receive a `onCompleted` parameter that will be executed when the mutation completes, and will be passed the mutation results. You can set this in state. This is probably a bad practice, and shouldn't be relied on. + +## Being notified when a mutation completes + +The `loadFragmentReference` (aka `loadMutation` in these docs) function accepts a `FetchOptions` object, which can include an `onCompleted` callback, e.g.: + +```jsx +loadMutation( + { + input: { + id: pet.id, + tagline: 'SUPER DOG', + }, + }, + { + onCompleted: () => { + console.log('Done setting'); + }, + }, +); +``` + +This `onCompleted` callback takes no parameters. **If you need to receive the results of the mutation, please comment on [this issue](https://github.com/isographlabs/isograph/issues/277).** + +## Being notified when a mutation errors out + +`FetchOptions` also accepts an `onError` callback: + +```jsx +loadMutation( + { + input: { + id: pet.id, + tagline: 'SUPER DOG', + }, + }, + { + onError: () => { + console.log('Oops'); + }, + }, +); +``` diff --git a/libs/isograph-react/src/core/cache.ts b/libs/isograph-react/src/core/cache.ts index 2e796c250..956646eec 100644 --- a/libs/isograph-react/src/core/cache.ts +++ b/libs/isograph-react/src/core/cache.ts @@ -34,7 +34,7 @@ import { mergeObjectsUsingReaderAst } from './areEqualWithDeepComparison'; import { maybeMakeNetworkRequest } from './makeNetworkRequest'; import { wrapResolvedValue } from './PromiseWrapper'; import { logMessage } from './logging'; -import { DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check'; +import { FetchOptions } from './check'; export const TYPENAME_FIELD_NAME = '__typename'; @@ -95,12 +95,11 @@ export function getOrCreateCacheForArtifact< entrypoint.networkRequestInfo.queryText + JSON.stringify(stableCopy(variables)); const factory = () => { - const shouldFetch = fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, variables, - shouldFetch, + fetchOptions, ); const itemCleanupPair: ItemCleanupPair< diff --git a/libs/isograph-react/src/core/check.ts b/libs/isograph-react/src/core/check.ts index 24d5234d5..c2afbed75 100644 --- a/libs/isograph-react/src/core/check.ts +++ b/libs/isograph-react/src/core/check.ts @@ -15,6 +15,8 @@ export const DEFAULT_SHOULD_FETCH_VALUE: ShouldFetch = 'IfNecessary'; export type FetchOptions = { shouldFetch?: ShouldFetch; + onComplete?: () => void; + onError?: () => void; }; export type CheckResult = diff --git a/libs/isograph-react/src/core/makeNetworkRequest.ts b/libs/isograph-react/src/core/makeNetworkRequest.ts index 200ead4fc..0aa2f3040 100644 --- a/libs/isograph-react/src/core/makeNetworkRequest.ts +++ b/libs/isograph-react/src/core/makeNetworkRequest.ts @@ -19,7 +19,7 @@ import { } from './PromiseWrapper'; import { normalizeData } from './cache'; import { logMessage } from './logging'; -import { check, ShouldFetch } from './check'; +import { check, DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check'; let networkRequestId = 0; @@ -27,11 +27,11 @@ export function maybeMakeNetworkRequest( environment: IsographEnvironment, artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint, variables: Variables, - shouldFetch: ShouldFetch, + fetchOptions?: FetchOptions, ): ItemCleanupPair> { - switch (shouldFetch) { + switch (fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE) { case 'Yes': { - return makeNetworkRequest(environment, artifact, variables); + return makeNetworkRequest(environment, artifact, variables, fetchOptions); } case 'No': { return [wrapResolvedValue(undefined), () => {}]; @@ -49,7 +49,12 @@ export function maybeMakeNetworkRequest( if (result.kind === 'EnoughData') { return [wrapResolvedValue(undefined), () => {}]; } else { - return makeNetworkRequest(environment, artifact, variables); + return makeNetworkRequest( + environment, + artifact, + variables, + fetchOptions, + ); } } } @@ -59,6 +64,7 @@ export function makeNetworkRequest( environment: IsographEnvironment, artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint, variables: Variables, + fetchOptions?: FetchOptions, ): ItemCleanupPair> { // TODO this should be a DataId and stored in the store const myNetworkRequestId = networkRequestId + ''; @@ -85,6 +91,9 @@ export function makeNetworkRequest( }); if (networkResponse.errors != null) { + try { + fetchOptions?.onError?.(); + } catch {} // @ts-expect-error Why are we getting the wrong constructor here? throw new Error('GraphQL network response had errors', { cause: networkResponse, @@ -114,6 +123,16 @@ export function makeNetworkRequest( }; retainQuery(environment, retainedQuery); } + + try { + fetchOptions?.onComplete?.(); + } catch {} + }) + .catch((e) => { + try { + fetchOptions?.onError?.(); + } catch {} + throw e; }); const wrapper = wrapPromise(promise); diff --git a/libs/isograph-react/src/core/read.ts b/libs/isograph-react/src/core/read.ts index b553db2a4..b5152a42a 100644 --- a/libs/isograph-react/src/core/read.ts +++ b/libs/isograph-react/src/core/read.ts @@ -33,7 +33,7 @@ import { ReaderAst } from './reader'; import { Arguments } from './util'; import { logMessage } from './logging'; import { CleanupFn } from '@isograph/disposable-types'; -import { DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check'; +import { FetchOptions } from './check'; export type WithEncounteredRecords = { readonly encounteredRecords: EncounteredIds; @@ -483,14 +483,12 @@ function readData( const fragmentReferenceAndDisposeFromEntrypoint = ( entrypoint: IsographEntrypoint, ): [FragmentReference, CleanupFn] => { - const shouldFetch = - fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, localVariables, - shouldFetch, + fetchOptions, ); const fragmentReference: FragmentReference = { @@ -547,15 +545,12 @@ function readData( if ( entrypointLoaderState.kind === 'EntrypointNotLoaded' ) { - const shouldFetch = - fetchOptions?.shouldFetch ?? - DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, localVariables, - shouldFetch, + fetchOptions, ); entrypointLoaderState = { kind: 'NetworkRequestStarted', diff --git a/libs/isograph-react/src/react/useImperativeReference.ts b/libs/isograph-react/src/react/useImperativeReference.ts index d12bee69c..09e6d0aaa 100644 --- a/libs/isograph-react/src/react/useImperativeReference.ts +++ b/libs/isograph-react/src/react/useImperativeReference.ts @@ -11,7 +11,7 @@ import { useIsographEnvironment } from './IsographEnvironmentProvider'; import { ROOT_ID } from '../core/IsographEnvironment'; import { maybeMakeNetworkRequest } from '../core/makeNetworkRequest'; import { wrapResolvedValue } from '../core/PromiseWrapper'; -import { DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from '../core/check'; +import { FetchOptions } from '../core/check'; // TODO rename this to useImperativelyLoadedEntrypoint @@ -40,13 +40,11 @@ export function useImperativeReference< variables: ExtractParameters, fetchOptions?: FetchOptions, ) => { - const shouldFetch = - fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE; const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, entrypoint, variables, - shouldFetch, + fetchOptions, ); setState([ { From 2ad9fd4482c4b10d7d747ee15d79932fe9cf52a0 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 13 Nov 2024 23:17:17 -0500 Subject: [PATCH 11/61] add more to documentation --- docs-website/docs/mutation.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs-website/docs/mutation.md b/docs-website/docs/mutation.md index 21a264dc7..c4932050b 100644 --- a/docs-website/docs/mutation.md +++ b/docs-website/docs/mutation.md @@ -137,7 +137,7 @@ The following methods will be available in the future: - When reading a fragment reference is not a hook (see [this issue](https://github.com/isographlabs/isograph/issues/273)), you should be able to read the fragment conditionally in the parent component. -## Being notified when a mutation completes +### Being notified when a mutation completes The `loadFragmentReference` (aka `loadMutation` in these docs) function accepts a `FetchOptions` object, which can include an `onCompleted` callback, e.g.: @@ -159,7 +159,7 @@ loadMutation( This `onCompleted` callback takes no parameters. **If you need to receive the results of the mutation, please comment on [this issue](https://github.com/isographlabs/isograph/issues/277).** -## Being notified when a mutation errors out +### Being notified when a mutation errors out `FetchOptions` also accepts an `onError` callback: @@ -178,3 +178,7 @@ loadMutation( }, ); ``` + +### Appending to a list in response to a mutation + +There are no good APIs to do this, currently. If you need this behavior, please [comment on this issue](https://github.com/isographlabs/isograph/issues/278) or reach out in the Discord. We're happy to help you out! From 476bde5fd5da3f8315f9d9236bac22c887c993d0 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 13 Nov 2024 23:21:37 -0500 Subject: [PATCH 12/61] reshuffle sidebars --- docs-website/sidebars.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs-website/sidebars.ts b/docs-website/sidebars.ts index c887ca49b..b1c9194b4 100644 --- a/docs-website/sidebars.ts +++ b/docs-website/sidebars.ts @@ -19,10 +19,12 @@ const sidebars: SidebarsConfig = { 'loadable-fields', 'pagination', 'mutation', - 'expose-field-directives', - 'refetching', - 'isograph-rules', 'faq', + { + type: 'category', + label: 'Deprecated features', + items: ['expose-field-directives', 'refetching'], + }, { type: 'category', label: 'How Isograph works', @@ -40,6 +42,11 @@ const sidebars: SidebarsConfig = { }, 'development-workflow', 'backlog', + { + type: 'category', + label: 'Miscellaneous', + items: ['isograph-rules'], + }, ], }; From 80260e5b8b167fe7e9e9268aad261c5090901d8f Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 13 Nov 2024 23:34:33 -0500 Subject: [PATCH 13/61] add docs on conditional fetching --- docs-website/docs/conditional-fetching.md | 36 +++++++++++++++++++++++ docs-website/sidebars.ts | 1 + 2 files changed, 37 insertions(+) create mode 100644 docs-website/docs/conditional-fetching.md diff --git a/docs-website/docs/conditional-fetching.md b/docs-website/docs/conditional-fetching.md new file mode 100644 index 000000000..45a3185f3 --- /dev/null +++ b/docs-website/docs/conditional-fetching.md @@ -0,0 +1,36 @@ +# Conditionally fetching + +`useLazyReference`, `useImperativeReference`, `useClientSideDefer` and `useImperativeLoadableField` all accept a `FetchOptions` parameter. + +You can use this parameter to control whether to make a network request. For example: + +```jsx +const { fragmentReference: mutationRef, loadFragmentReference: loadMutation } = + useImperativeReference(iso(`entrypoint Mutation.SetTagline`)); + +const onClick = () => { + loadMutation( + // parameters + {}, + // FetchOptions + { + shouldFetch: 'Yes', + }, + ); +}; +``` + +`shouldFetch` can be `"Yes"`, `"No"` or `"IfNecessary"`. + +`"Yes"` forces the network request to be made. `"IfNecessary"` will avoid making the network request if there is sufficient data in the Isograph store to fulfill the request, and `"No"` will not make the network request. + +:::note +This is called a "fetch policy" in Relay. +::: + +## Why would I want `shouldFetch`: `"No"`? + +Two reasons: + +- The hooks `useLazyReference` and `useClientSideDefer` cannot be called conditionally. Passing `"No"` is a way of avoiding the network request, despite calling the hooks unconditionally. +- Otherwise, this isn't a very useful feature currently, because reading a fragment with missing data will cause it to suspend. However, in the future, we will loosen this requirement (i.e. you will be able to read fragments with missing data.) In that world, avoiding making a network request and rendering whatever data happens to be availabe in the Isograph store will be possible, and may even be useful in certain circumstances. diff --git a/docs-website/sidebars.ts b/docs-website/sidebars.ts index b1c9194b4..fc02f0afe 100644 --- a/docs-website/sidebars.ts +++ b/docs-website/sidebars.ts @@ -19,6 +19,7 @@ const sidebars: SidebarsConfig = { 'loadable-fields', 'pagination', 'mutation', + 'conditional-fetching', 'faq', { type: 'category', From d5e419cd7b967b5a0fda9567e65729f364fdd699 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 14 Nov 2024 00:07:46 -0500 Subject: [PATCH 14/61] add parameters docs --- docs-website/docs/parameters.md | 63 +++++++++++++++++++++++++++++++++ docs-website/sidebars.ts | 1 + 2 files changed, 64 insertions(+) create mode 100644 docs-website/docs/parameters.md diff --git a/docs-website/docs/parameters.md b/docs-website/docs/parameters.md new file mode 100644 index 000000000..ec290d20e --- /dev/null +++ b/docs-website/docs/parameters.md @@ -0,0 +1,63 @@ +# Parameters + +## Parameters and client fields + +Unlike in GraphQL, there are no global variables (yet!) in Isograph. Instead, all parameters used in a client field must be defined in that client field, and no parameters can be unused. Example: + +```jsx +export const PetDetailDeferredRouteComponent = iso(` + field Query.PetDetailRoute($id: ID!) @component { + pet(id: $id) { + PetDetail + } + } +`)(function PetDetailRouteComponent({ data }) { + // ... +}); +``` + +## Accessing parameters at runtime + +The parameters with which a client field was read can be accessed as part of that first parameter. For example: + +```jsx +export const PetDetailDeferredRouteComponent = iso(` + field Query.PetDetailRoute($id: ID!) @component { + pet(id: $id) { + PetDetail + } + } +`)(function PetDetailRouteComponent({ data, parameters }) { + console.log('hello from pet ' + parameters.id + '!'); + // ... +}); +``` + +## Loadable fields are variables + +Parameters can be omitted in isograph literals from loadably selected client fields. Those missing variables become parameters that you must pass when making the network request (i.e. loading the field): + +```jsx +export const BlogItem = iso(` + field BlogItem.BlogItemDisplay @component { + # Assume that BlogItemMoreDetail accepts an includeImages: Boolean! parameter + # that we are skipping here, which is allowed because the field is selected + # loadably: + BlogItemMoreDetail @loadable(lazyLoadArtifact: true) + } +`)(({ data: blogItem }) => { + const { fragmentReference, loadField } = useImperativeLoadableField( + blogItem.BlogItemMoreDetail, + ); + + const loadBlogItem = () => + loadField({ + // now, includeImages must be passed here: + includeImages: true, + }); +}); +``` + +## Typechecking + +There is no typechecking of variables, except to inasmuch as nullable variables are allowed to be missing. This feature is coming soon! diff --git a/docs-website/sidebars.ts b/docs-website/sidebars.ts index fc02f0afe..43115728b 100644 --- a/docs-website/sidebars.ts +++ b/docs-website/sidebars.ts @@ -20,6 +20,7 @@ const sidebars: SidebarsConfig = { 'pagination', 'mutation', 'conditional-fetching', + 'parameters', 'faq', { type: 'category', From f00b5b0552a2a1922475b590661bd02664b82f44 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 14 Nov 2024 00:16:54 -0500 Subject: [PATCH 15/61] update pagination docs --- docs-website/docs/pagination.md | 32 ++++++++++++++++++- .../useConnectionSpecPagination.ts | 6 ++-- .../loadable-hooks/useSkipLimitPagination.ts | 4 +-- 3 files changed, 36 insertions(+), 6 deletions(-) diff --git a/docs-website/docs/pagination.md b/docs-website/docs/pagination.md index f77ca4c63..e32e565a1 100644 --- a/docs-website/docs/pagination.md +++ b/docs-website/docs/pagination.md @@ -2,6 +2,10 @@ Loadable fields can also be used to paginate. +:::note +This documentation demonstrates the `useSkipLimitPagination` hook. You can also use `useConnectionSpecPagination` if your connection field conforms to the [Relay connection spec](https://facebook.github.io/relay/graphql/connections.htm). +::: + :::note This API is likely to get simplified substantially, as we support `@loadable` on linked server fields. ::: @@ -82,4 +86,30 @@ export const PetDetailDeferredRouteComponent = iso(` }); ``` -You can also use `useConnectionSpecPagination` if your connection field conforms to the [Relay connection spec](https://facebook.github.io/relay/graphql/connections.htm). +## Prefetching initial pages + +Each pagination hook accepts a second `initialState` parameter. You can fetch data as part of the parent query (or anywhere, in fact!) and pass the appropriate value to that `initialState`. Consider: + +```jsx +export const Newsfeed = iso(` + field Query.Newsfeed @component { + viewer { + newsfeed(skip: 0, limit: 6) { + NewsfeedAdOrBlog + } + NewsfeedPaginationComponent @loadable + } + } +`)(function PetDetailRouteComponent({ data }) { + const viewer = data.viewer; + + const paginationState = useSkipLimitPagination( + viewer.NewsfeedPaginationComponent, + { skip: viewer.newsfeed.length }, + ); + + const newsfeedItems = viewer.newsfeed.concat(paginationState.results); + + // ... +}); +``` diff --git a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts index c4d3a0694..9c6723698 100644 --- a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts @@ -91,7 +91,7 @@ export function useConnectionSpecPagination< Connection, UseConnectionSpecPaginationArgs >, - pageInfo?: PageInfo, + initialState?: PageInfo, ): UsePaginationReturnValue { const networkRequestOptions = { suspendIfInFlight: true, @@ -276,9 +276,9 @@ export function useConnectionSpecPagination< if (!networkRequestStatus) { return { kind: 'Complete', - fetchMore: getFetchMore(pageInfo?.endCursor ?? null), + fetchMore: getFetchMore(initialState?.endCursor ?? null), results: [], - hasNextPage: pageInfo?.hasNextPage ?? true, + hasNextPage: initialState?.hasNextPage ?? true, }; } diff --git a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts index 24bcc4a20..46a077fba 100644 --- a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts @@ -81,7 +81,7 @@ export function useSkipLimitPagination< ReadonlyArray, UseSkipLimitPaginationArgs >, - initialArgs?: { + initialState?: { skip?: number | void | null; }, ): UseSkipLimitReturnValue { @@ -259,7 +259,7 @@ export function useSkipLimitPagination< if (!networkRequestStatus) { return { kind: 'Complete', - fetchMore: getFetchMore(initialArgs?.skip ?? 0), + fetchMore: getFetchMore(initialState?.skip ?? 0), results: [], }; } From 46a5cdad713543c815970fdfff99ded0e9d5a5ca Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 14 Nov 2024 00:19:38 -0500 Subject: [PATCH 16/61] link to moar from quickstart --- docs-website/docs/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-website/docs/quickstart.md b/docs-website/docs/quickstart.md index 9a8e4b329..25dabdc60 100644 --- a/docs-website/docs/quickstart.md +++ b/docs-website/docs/quickstart.md @@ -462,6 +462,6 @@ Now, if you refresh, the UI will be divided into subcomponents and look exactly Congratulations! You just built your first Isograph app. -Want more? Try extracting the sorted list of films into its own client field (no need to use `@component` for this one.) Use hooks in your components (they work!) Check out the [mutation](/docs/mutation/) documentation to learn about how Isograph lets you update your data. +Want more? Try extracting the sorted list of films into its own client field (no need to use `@component` for this one.) Use hooks in your components (they work!) Check out the [mutation](/docs/mutation/), [pagination](/docs/pagination/), and the [loadable fields](/docs/loadable-fields/) documentation! Or, [join the Discord](https://discord.gg/qcHUxb6deQ)! From e939e92931ce4a9942c59138e91c28cf6469d05f Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 14 Nov 2024 00:24:33 -0500 Subject: [PATCH 17/61] update docs --- docs-website/docs/loadable-fields.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs-website/docs/loadable-fields.md b/docs-website/docs/loadable-fields.md index dc04dd809..71fd700f4 100644 --- a/docs-website/docs/loadable-fields.md +++ b/docs-website/docs/loadable-fields.md @@ -2,11 +2,13 @@ ## Overview -Client fields can be selected loadably. If a client field is selected loadably, the resolver will not receive the client field result directly, but will instead receive a `LoadableField`. +Loadable fields are a fundamental Isograph concept. -The `LoadableField` is a wrapper around a function that, when called, will make a network request for the data needed by the client field. Making the network request creates a `FragmentReference` that must be disposed, in order to prevent memory leaks. +Client fields can be selected loadably (e.g. `BlogBody @loadable`). This means that the data for that `BlogBody` client field will not be included in the parent network request. Instead, the outer client field (`BlogPostDisplay`) will receive a `LoadableField` in place of that loadably selected client field. -As a result, usually, you should not call the `LoadableField` directly. Instead, pass it to a hook that knows what to do with it! +This `LoadableField` is a wrapper around a function that, when called, will make a network request for the data needed by the `BlogPost` client field. + +You usually would not call the `LoadableField` yourself. Instead, pass it to a hook that knows what to do with it! ## Basic walk-through @@ -169,4 +171,4 @@ export const BlogPostDisplay = iso(` ## Pagination -See [the pagination docs](../pagination). +Pagination is also built on loadable fields. See [the pagination docs](../pagination). From 156da62121719f92d93cf4adfb0c5f787dd23cad Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 14 Nov 2024 23:06:53 -0500 Subject: [PATCH 18/61] extract repository row into its own component --- .../UserRepositoryList.tsx | 61 +++++++------ .../__isograph/Query/HomePage/__refetch__0.ts | 1 - .../__isograph/Query/HomePage/entrypoint.ts | 1 - .../Query/PullRequest/entrypoint.ts | 1 - .../Query/RepositoryPage/entrypoint.ts | 1 - .../__isograph/Query/UserPage/entrypoint.ts | 1 - .../Repository/RepositoryRow/output_type.ts | 4 + .../Repository/RepositoryRow/param_type.ts | 46 ++++++++++ .../RepositoryRow/resolver_reader.ts | 86 +++++++++++++++++++ .../User/RepositoryConnection/entrypoint.ts | 1 - .../User/RepositoryConnection/param_type.ts | 42 +-------- .../RepositoryConnection/resolver_reader.ts | 68 +-------------- .../src/isograph-components/__isograph/iso.ts | 5 ++ 13 files changed, 183 insertions(+), 135 deletions(-) create mode 100644 demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/output_type.ts create mode 100644 demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/param_type.ts create mode 100644 demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/resolver_reader.ts diff --git a/demos/github-demo/src/isograph-components/UserRepositoryList.tsx b/demos/github-demo/src/isograph-components/UserRepositoryList.tsx index b50f6c242..0beef454b 100644 --- a/demos/github-demo/src/isograph-components/UserRepositoryList.tsx +++ b/demos/github-demo/src/isograph-components/UserRepositoryList.tsx @@ -43,19 +43,7 @@ export const RepositoryList = iso(` return null; } const { node } = data; - return ( - - - - {node.nameWithOwner} - - - {node.stargazerCount} - {node.forkCount} - {node.pullRequests?.totalCount} - {node.watchers?.totalCount} - - ); + return ; })} @@ -93,19 +81,8 @@ export const RepositoryConnection = iso(` } edges { node { + RepositoryRow id - RepositoryLink - name - nameWithOwner - description - forkCount - pullRequests { - totalCount - } - stargazerCount - watchers { - totalCount - } } } } @@ -113,3 +90,37 @@ export const RepositoryConnection = iso(` `)(function UserRepositoryConnectionComponent({ data }) { return data.repositories; }); + +export const RepositoryRow = iso(` + field Repository.RepositoryRow @component { + RepositoryLink + name + nameWithOwner + description + forkCount + pullRequests { + totalCount + } + stargazerCount + watchers { + totalCount + } + } +`)(( + { data: repository }, + { setRoute }: { setRoute: (route: Route) => void }, +) => { + return ( + + + + {repository.nameWithOwner} + + + {repository.stargazerCount} + {repository.forkCount} + {repository.pullRequests?.totalCount} + {repository.watchers?.totalCount} + + ); +}); diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts index 6373b761a..877075fda 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts @@ -222,7 +222,6 @@ const artifact: RefetchQueryNormalizationArtifact = { queryText, normalizationAst, }, - concreteType: "Query", }; export default artifact; diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts index 362f12223..4b1df6c03 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts @@ -214,7 +214,6 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, - concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts index 75db112b5..1e76b47d4 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts @@ -181,7 +181,6 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, - concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts index 6be9f97c9..b7650f92b 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts @@ -316,7 +316,6 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, - concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts index 348b9cb3f..17cf6153b 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts @@ -232,7 +232,6 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, - concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/output_type.ts b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/output_type.ts new file mode 100644 index 000000000..85aabc0f1 --- /dev/null +++ b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/output_type.ts @@ -0,0 +1,4 @@ +import type { ExtractSecondParam, CombineWithIntrinsicAttributes } from '@isograph/react'; +import type React from 'react'; +import { RepositoryRow as resolver } from '../../../UserRepositoryList'; +export type Repository__RepositoryRow__output_type = (React.FC>>); diff --git a/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/param_type.ts b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/param_type.ts new file mode 100644 index 000000000..f84cb5a5b --- /dev/null +++ b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/param_type.ts @@ -0,0 +1,46 @@ +import { type Repository__RepositoryLink__output_type } from '../../Repository/RepositoryLink/output_type'; + +export type Repository__RepositoryRow__param = { + readonly data: { + readonly RepositoryLink: Repository__RepositoryLink__output_type, + /** +The name of the repository. + */ + readonly name: string, + /** +The repository's name with owner. + */ + readonly nameWithOwner: string, + /** +The description of the repository. + */ + readonly description: (string | null), + /** +Returns how many forks there are of this repository in the whole network. + */ + readonly forkCount: number, + /** +A list of pull requests that have been opened in the repository. + */ + readonly pullRequests: { + /** +Identifies the total count of items in the connection. + */ + readonly totalCount: number, + }, + /** +Returns a count of how many stargazers there are on this object + */ + readonly stargazerCount: number, + /** +A list of users watching the repository. + */ + readonly watchers: { + /** +Identifies the total count of items in the connection. + */ + readonly totalCount: number, + }, + }, + readonly parameters: Record, +}; diff --git a/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/resolver_reader.ts b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/resolver_reader.ts new file mode 100644 index 000000000..77c50b638 --- /dev/null +++ b/demos/github-demo/src/isograph-components/__isograph/Repository/RepositoryRow/resolver_reader.ts @@ -0,0 +1,86 @@ +import type {ComponentReaderArtifact, ExtractSecondParam, ReaderAst } from '@isograph/react'; +import { Repository__RepositoryRow__param } from './param_type'; +import { RepositoryRow as resolver } from '../../../UserRepositoryList'; +import Repository__RepositoryLink__resolver_reader from '../../Repository/RepositoryLink/resolver_reader'; + +const readerAst: ReaderAst = [ + { + kind: "Resolver", + alias: "RepositoryLink", + arguments: null, + readerArtifact: Repository__RepositoryLink__resolver_reader, + usedRefetchQueries: [], + }, + { + kind: "Scalar", + fieldName: "name", + alias: null, + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + alias: null, + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + alias: null, + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + alias: null, + arguments: null, + }, + { + kind: "Linked", + fieldName: "pullRequests", + alias: null, + arguments: null, + condition: null, + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + alias: null, + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + alias: null, + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + alias: null, + arguments: null, + condition: null, + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + alias: null, + arguments: null, + }, + ], + }, +]; + +const artifact: ComponentReaderArtifact< + Repository__RepositoryRow__param, + ExtractSecondParam +> = { + kind: "ComponentReaderArtifact", + componentName: "Repository.RepositoryRow", + resolver, + readerAst, +}; + +export default artifact; diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts index 1c2e894c5..c0cb4c845 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts @@ -212,7 +212,6 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, - concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts index f56039b53..5d76bb0c9 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/param_type.ts @@ -1,4 +1,4 @@ -import { type Repository__RepositoryLink__output_type } from '../../Repository/RepositoryLink/output_type'; +import { type Repository__RepositoryRow__output_type } from '../../Repository/RepositoryRow/output_type'; import type { User__RepositoryConnection__parameters } from './parameters_type'; export type User__RepositoryConnection__param = { @@ -28,49 +28,11 @@ A list of edges. The item at the end of the edge. */ readonly node: ({ + readonly RepositoryRow: Repository__RepositoryRow__output_type, /** The Node ID of the Repository object */ readonly id: string, - readonly RepositoryLink: Repository__RepositoryLink__output_type, - /** -The name of the repository. - */ - readonly name: string, - /** -The repository's name with owner. - */ - readonly nameWithOwner: string, - /** -The description of the repository. - */ - readonly description: (string | null), - /** -Returns how many forks there are of this repository in the whole network. - */ - readonly forkCount: number, - /** -A list of pull requests that have been opened in the repository. - */ - readonly pullRequests: { - /** -Identifies the total count of items in the connection. - */ - readonly totalCount: number, - }, - /** -Returns a count of how many stargazers there are on this object - */ - readonly stargazerCount: number, - /** -A list of users watching the repository. - */ - readonly watchers: { - /** -Identifies the total count of items in the connection. - */ - readonly totalCount: number, - }, } | null), } | null)> | null), }, diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts index 11105198c..764955150 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/resolver_reader.ts @@ -2,7 +2,7 @@ import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; import { User__RepositoryConnection__param } from './param_type'; import { User__RepositoryConnection__output_type } from './output_type'; import { RepositoryConnection as resolver } from '../../../UserRepositoryList'; -import Repository__RepositoryLink__resolver_reader from '../../Repository/RepositoryLink/resolver_reader'; +import Repository__RepositoryRow__resolver_reader from '../../Repository/RepositoryRow/resolver_reader'; const readerAst: ReaderAst = [ { @@ -57,78 +57,18 @@ const readerAst: ReaderAst = [ arguments: null, condition: null, selections: [ - { - kind: "Scalar", - fieldName: "id", - alias: null, - arguments: null, - }, { kind: "Resolver", - alias: "RepositoryLink", + alias: "RepositoryRow", arguments: null, - readerArtifact: Repository__RepositoryLink__resolver_reader, + readerArtifact: Repository__RepositoryRow__resolver_reader, usedRefetchQueries: [], }, { kind: "Scalar", - fieldName: "name", - alias: null, - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - alias: null, - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - alias: null, - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - alias: null, - arguments: null, - }, - { - kind: "Linked", - fieldName: "pullRequests", - alias: null, - arguments: null, - condition: null, - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - alias: null, - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - alias: null, - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", + fieldName: "id", alias: null, arguments: null, - condition: null, - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - alias: null, - arguments: null, - }, - ], }, ], }, diff --git a/demos/github-demo/src/isograph-components/__isograph/iso.ts b/demos/github-demo/src/isograph-components/__isograph/iso.ts index 4fe253da4..b9cc634c2 100644 --- a/demos/github-demo/src/isograph-components/__isograph/iso.ts +++ b/demos/github-demo/src/isograph-components/__isograph/iso.ts @@ -15,6 +15,7 @@ import { type Query__RepositoryPage__param } from './Query/RepositoryPage/param_ import { type Query__UserDetail__param } from './Query/UserDetail/param_type'; import { type Query__UserPage__param } from './Query/UserPage/param_type'; import { type Repository__RepositoryLink__param } from './Repository/RepositoryLink/param_type'; +import { type Repository__RepositoryRow__param } from './Repository/RepositoryRow/param_type'; import { type Starrable__IsStarred__param } from './Starrable/IsStarred/param_type'; import { type User__Avatar__param } from './User/Avatar/param_type'; import { type User__RepositoryConnection__param } from './User/RepositoryConnection/param_type'; @@ -136,6 +137,10 @@ export function iso( param: T & MatchesWhitespaceAndString<'field Repository.RepositoryLink', T> ): IdentityWithParamComponent; +export function iso( + param: T & MatchesWhitespaceAndString<'field Repository.RepositoryRow', T> +): IdentityWithParamComponent; + export function iso( param: T & MatchesWhitespaceAndString<'field Starrable.IsStarred', T> ): IdentityWithParamComponent; From accba6bd339b4eeedb9e818fac4e3f4d3e87c7d5 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 14 Nov 2024 23:31:24 -0500 Subject: [PATCH 19/61] simplify newsfeed pagination --- .../src/components/Newsfeed/NewsfeedRoute.tsx | 8 +++----- .../__isograph/Query/Newsfeed/param_type.ts | 5 +---- .../Query/Newsfeed/resolver_reader.ts | 19 +++++-------------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx index bcaa0764f..29eebd7df 100644 --- a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx +++ b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx @@ -12,9 +12,7 @@ import { ErrorBoundary } from '../ErrorBoundary'; export const Newsfeed = iso(` field Query.Newsfeed @component { viewer { - newsfeed(skip: 0, limit: 6) { - NewsfeedAdOrBlog - } + initial: NewsfeedPaginationComponent(skip: 0, limit: 6) NewsfeedPaginationComponent @loadable } } @@ -23,10 +21,10 @@ export const Newsfeed = iso(` const paginationState = useSkipLimitPagination( viewer.NewsfeedPaginationComponent, - { skip: viewer.newsfeed.length }, + { skip: viewer.initial.length }, ); - const newsfeedItems = viewer.newsfeed.concat(paginationState.results); + const newsfeedItems = viewer.initial.concat(paginationState.results); const loadMore = () => { if (paginationState.kind === 'Complete') { diff --git a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts index c26d3ed3e..d96c1a960 100644 --- a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts +++ b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/param_type.ts @@ -1,4 +1,3 @@ -import { type NewsfeedItem__NewsfeedAdOrBlog__output_type } from '../../NewsfeedItem/NewsfeedAdOrBlog/output_type'; import { type Viewer__NewsfeedPaginationComponent__output_type } from '../../Viewer/NewsfeedPaginationComponent/output_type'; import { type LoadableField, type ExtractParameters } from '@isograph/react'; import { type Viewer__NewsfeedPaginationComponent__param } from '../../Viewer/NewsfeedPaginationComponent/param_type'; @@ -6,9 +5,7 @@ import { type Viewer__NewsfeedPaginationComponent__param } from '../../Viewer/Ne export type Query__Newsfeed__param = { readonly data: { readonly viewer: { - readonly newsfeed: ReadonlyArray<{ - readonly NewsfeedAdOrBlog: NewsfeedItem__NewsfeedAdOrBlog__output_type, - }>, + readonly initial: Viewer__NewsfeedPaginationComponent__output_type, readonly NewsfeedPaginationComponent: LoadableField< Viewer__NewsfeedPaginationComponent__param, Viewer__NewsfeedPaginationComponent__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts index 226e8766c..7ea63fd18 100644 --- a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/resolver_reader.ts @@ -1,7 +1,7 @@ import type {ComponentReaderArtifact, ExtractSecondParam, ReaderAst } from '@isograph/react'; import { Query__Newsfeed__param } from './param_type'; import { Newsfeed as resolver } from '../../../Newsfeed/NewsfeedRoute'; -import NewsfeedItem__NewsfeedAdOrBlog__resolver_reader from '../../NewsfeedItem/NewsfeedAdOrBlog/resolver_reader'; +import Viewer__NewsfeedPaginationComponent__resolver_reader from '../../Viewer/NewsfeedPaginationComponent/resolver_reader'; import Viewer__NewsfeedPaginationComponent__entrypoint from '../../Viewer/NewsfeedPaginationComponent/entrypoint'; const readerAst: ReaderAst = [ @@ -13,9 +13,8 @@ const readerAst: ReaderAst = [ condition: null, selections: [ { - kind: "Linked", - fieldName: "newsfeed", - alias: null, + kind: "Resolver", + alias: "initial", arguments: [ [ "skip", @@ -27,16 +26,8 @@ const readerAst: ReaderAst = [ { kind: "Literal", value: 6 }, ], ], - condition: null, - selections: [ - { - kind: "Resolver", - alias: "NewsfeedAdOrBlog", - arguments: null, - readerArtifact: NewsfeedItem__NewsfeedAdOrBlog__resolver_reader, - usedRefetchQueries: [], - }, - ], + readerArtifact: Viewer__NewsfeedPaginationComponent__resolver_reader, + usedRefetchQueries: [], }, { kind: "LoadablySelectedField", From b336638c1940f0c052071cab00fff03640bd22ed Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Fri, 15 Nov 2024 00:10:21 -0500 Subject: [PATCH 20/61] add documentatiaon about 3D and isConcreteType fields --- docs-website/docs/abstract-types.md | 37 ++++ docs-website/docs/data-driven-dependencies.md | 163 ++++++++++++++++++ docs-website/docs/loadable-fields.md | 4 + docs-website/docs/pagination.md | 4 + docs-website/sidebars.ts | 16 +- 5 files changed, 216 insertions(+), 8 deletions(-) create mode 100644 docs-website/docs/abstract-types.md create mode 100644 docs-website/docs/data-driven-dependencies.md diff --git a/docs-website/docs/abstract-types.md b/docs-website/docs/abstract-types.md new file mode 100644 index 000000000..0b9619978 --- /dev/null +++ b/docs-website/docs/abstract-types.md @@ -0,0 +1,37 @@ +# Refining from abstract to concrete types + +Fields with an abstract type (e.g. `node`) have special `asConcreteType` fields. These will be null if the field does not have that concrete type. + +For example: + +```jsx +export const UserLink = iso(` + field Actor.ActorGreeting @component { + login + asUser { + twitterUsername + } + } +`)(function UserLinkComponent( + { data }, +) { + return <>Hello {data.login}{ + data.asUser != null + ? ` (who goes by ${data.asUser.twitterUsername ?? ''} on Twitter!)` + : null + } +}); +``` + +The above code will result in the following query text generated: + +```graphql +login +... on User { + twitterUsername +} +``` + +## Data-driven dependencies + +Check out the [data driven dependencies](/docs/data-driven-dependencies/) documentation to see how to combine [`@loadable` fields](/docs/loadable-fields/), [pagination](/docs/pagination/) and `asConcreteType` fields to fetch the minimal amount of data and JavaScript needed! diff --git a/docs-website/docs/data-driven-dependencies.md b/docs-website/docs/data-driven-dependencies.md new file mode 100644 index 000000000..d226213a4 --- /dev/null +++ b/docs-website/docs/data-driven-dependencies.md @@ -0,0 +1,163 @@ +# Data Driven Dependencies + +Congratulations! If you have gotten this far, you are about to learn about one of the coolest features of Isograph: the ability to load just the data and JavaScript that you need for a given page. + +To pull this off, we'll combine [`@loadable` fields](/docs/loadable-fields/), [pagination](/docs/pagination/) and [`asConcreteType`](/docs/abstract-types/) fields, so feel free to brush up on those before reading on. + +## Setting the scene + +Imagine you have a news feed containing text, images, videos, polls, events, songs, and albums, and not to mention variations of those that are only shown to users in some experiment or other. It is tremendously wasteful to download all of the JavaScript required to render an object of each type — very few or no users will see all possible variations! + +And not only does that waste your users' bandwidth, it also slows down how fast your page can start, as parsing all of that JavaScript isn't free. + +What can be done about it? Well, we should avoid loading the JavaScript for any news feed item types that we don't encounter. We call this "data driven dependencies", or 3D. + +And you guessed it — Isograph makes that easy! + +## Isograph makes it easy + +Let's see how that's done with Isograph. First, we use the `useConnectionSpecPagination` hook to fetch some news items: + +```jsx +export const Newsfeed = iso(` + field Query.Newsfeed @component { + firstPage: NewsfeedConnection(first: 10) + NewsfeedConnection @loadable + } +`)(function NewsfeedComponent({ data }) { + const pagination = useConnectionSpecPagination( + data.NewsfeedConnection, + data.firstPage.pageInfo, + ); + const repositories = (data.firstPage.edges ?? []).concat(pagination.results); + return repositories.map((data) => { + if (data == null || data.node == null) { + return null; + } + const { node } = data; + return ; + }); +}); + +export const NewsfeedPaginationComponent = iso(` + field Viewer.NewsfeedPaginationComponent($skip: Int!, $limit: Int!) { + newsfeed(skip: $skip, limit: $limit) { + NewsfeedItem + } + } +`)(({ data }) => { + return data.newsfeed; +}); +``` + +Here, we're also pre-fetching the first 10 items (via `firstPage`). + +### `NewsfeedRow` + +In our schema, `newsfeed` has type `NewsfeedItem`, which is a union of `TextItem`, `PhotoItem` and others. Armed with that knowledge, let's go ahead and define `NewsfeedItem`! + +```jsx +export const NewsfeedAdOrBlog = iso(` + field NewsfeedItem.NewsfeedAdOrBlog @component { + asTextItem { + TextItemDisplay + } + asPhotoItem { + PhotoItemDisplay + } + asRareItem { + TopSecretDisplay + } + } +`)(({ data: newsfeedItem }) => { + if (newsfeedItem.asTextItem != null) { + return ; + } else if (newsfeedItem.asPhotoItem != null) { + return ; + } else if (newsfeedItem.asRareItem != null) { + return ; + } + + // oops, it's a type we can't handle! Return null to not impact the + // the UI. + return null; +}); +``` + +That's great! Now, we render something different for each type of newsfeed item we encounter. + +### But wait + +But wait — wasn't the point of this to not load the JavaScript for the types of items we don't encounter? Yes! Let's do that, starting with `RareItem` and `TopSecretDisplay` (though you can, and should, do this for every variant.) + +We start by replacing `TopSecretDisplay` with `TopSecretDisplayWrapper`. (This will not be necessary once [this issue](https://github.com/isographlabs/isograph/issues/273) is tackled!) `TopSecretDisplayWrapper` is defined as follows: + +```jsx +export const TopSecretDisplayWrapper = iso(` + field RareItem.TopSecretDisplayWrapper @component { + TopSecretDisplay @loadable(lazyLoadArtifact: true) + } +`)(({ data }) => { + const { fragmentReference } = useClientSideDefer(data.TopSecretDisplay); + return ( + + + + ); +}); +``` + +Nice! Now, we only download the JavaScript for `TopSecretDisplay` if we actually encounter an item of the type `RareItem`. So, we've saved some bandwidth! + +### All is not well + +Unfortunately, we've introduced a regression. Now, not only is the JavaScript for `TopSecretDisplay` fetched only when needed, but the data for `TopSecretDisplay` is fetched only when needed. But GraphQL already gives us a way to fetch that data only if we encounter a newsfeed item with the `RareItem` type — inline fragments! And that's what our `asRareItem` selection compiles to: + +```graphql +... on RareItem { + # fields needed by TopSecretDisplay +} +``` + +:::note +Making this easy (i.e. by supporting `@loadable(lazyLoadArtifact: true, lazyLoadData: false)` or the like) is on the roadmap! This is a temporary workaround. +::: + +So, is there an inevitable tradeoff? No. We can restructure our app slightly to avoid this problem. We can restructure our `TopSecretDisplay` to take its data separately. Then, we select another field `TopSecretDisplayData` that provides that data! See how this all fits together: + +```jsx +export const NewsfeedAdOrBlog = iso(` + field NewsfeedItem.NewsfeedAdOrBlog @component { + asRareItem { + TopSecretDisplayData + TopSecretDisplayWrapper + } + # etc + } +`)(({ data: newsfeedItem }) => { + if (newsfeedItem.asRareItem != null) { + return ( + + ); + } + // etc +}); +``` + +`TopSecretDisplay` no longer selects any data of its own. + +Nice! Now, we fetch the data for `TopSecretDisplay` as part of the parent query and only if we encounter a newsfeed item with type `RareItem`, and load the JavaScript for `TopSecretDisplay` only if we encounter an item of this type! + +Neat! + +## Conclusion + +So, let's describe what we did. We fetched a list of newsfeed items. We used `asConcreteType` fields to allow us to refine a given concrete type, and then fetched the JavaScript for the given renderer only if we encountered an item of that type! + +And we did it all in userland! + +## PS + +We can also use these techniques (minus the pagination) to conditionally fetch JavaScript for fields that are nullable, and not just for fields that have an interface/union type! diff --git a/docs-website/docs/loadable-fields.md b/docs-website/docs/loadable-fields.md index 71fd700f4..561976a2d 100644 --- a/docs-website/docs/loadable-fields.md +++ b/docs-website/docs/loadable-fields.md @@ -172,3 +172,7 @@ export const BlogPostDisplay = iso(` ## Pagination Pagination is also built on loadable fields. See [the pagination docs](../pagination). + +## Data-driven dependencies + +Check out the [data driven dependencies](/docs/data-driven-dependencies/) documentation to see how to combine `@loadable` fields, [pagination](/docs/pagination/) and [`asConcreteType` fields](/docs/abstract-types/) to fetch the minimal amount of data and JavaScript needed! diff --git a/docs-website/docs/pagination.md b/docs-website/docs/pagination.md index e32e565a1..b36b19f48 100644 --- a/docs-website/docs/pagination.md +++ b/docs-website/docs/pagination.md @@ -113,3 +113,7 @@ export const Newsfeed = iso(` // ... }); ``` + +## Data-driven dependencies + +Check out the [data driven dependencies](/docs/data-driven-dependencies/) documentation to see how to combine [`@loadable` fields](/docs/loadable-fields/), pagination and [`asConcreteType` fields](/docs/abstract-types/) to fetch the minimal amount of data and JavaScript needed! diff --git a/docs-website/sidebars.ts b/docs-website/sidebars.ts index 43115728b..00cbb01aa 100644 --- a/docs-website/sidebars.ts +++ b/docs-website/sidebars.ts @@ -20,13 +20,10 @@ const sidebars: SidebarsConfig = { 'pagination', 'mutation', 'conditional-fetching', + 'abstract-types', + 'data-driven-dependencies', 'parameters', 'faq', - { - type: 'category', - label: 'Deprecated features', - items: ['expose-field-directives', 'refetching'], - }, { type: 'category', label: 'How Isograph works', @@ -42,12 +39,15 @@ const sidebars: SidebarsConfig = { label: 'Design docs', items: ['design-docs/incremental-compilation'], }, - 'development-workflow', - 'backlog', { type: 'category', label: 'Miscellaneous', - items: ['isograph-rules'], + items: ['isograph-rules', 'development-workflow', 'backlog'], + }, + { + type: 'category', + label: 'Deprecated features', + items: ['expose-field-directives', 'refetching'], }, ], }; From 053b9176947cfc14d70f19eecb0f9bdffa2fa634 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Fri, 15 Nov 2024 00:12:14 -0500 Subject: [PATCH 21/61] oops - things were build using a compiler from the wrong commit --- .../__isograph/Query/HomePage/__refetch__0.ts | 1 + .../isograph-components/__isograph/Query/HomePage/entrypoint.ts | 1 + .../__isograph/Query/PullRequest/entrypoint.ts | 1 + .../__isograph/Query/RepositoryPage/entrypoint.ts | 1 + .../isograph-components/__isograph/Query/UserPage/entrypoint.ts | 1 + .../__isograph/User/RepositoryConnection/entrypoint.ts | 1 + 6 files changed, 6 insertions(+) diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts index 877075fda..6373b761a 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts @@ -222,6 +222,7 @@ const artifact: RefetchQueryNormalizationArtifact = { queryText, normalizationAst, }, + concreteType: "Query", }; export default artifact; diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts index 4b1df6c03..362f12223 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts @@ -214,6 +214,7 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, + concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts index 1e76b47d4..75db112b5 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts @@ -181,6 +181,7 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, + concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts index b7650f92b..6be9f97c9 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts @@ -316,6 +316,7 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, + concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts index 17cf6153b..348b9cb3f 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts @@ -232,6 +232,7 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, + concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts index c0cb4c845..1c2e894c5 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts @@ -212,6 +212,7 @@ const artifact: IsographEntrypoint< queryText, normalizationAst, }, + concreteType: "Query", readerWithRefetchQueries: { kind: "ReaderWithRefetchQueries", nestedRefetchQueries, From 5bb6d5c8057e75dc70e96a9bdcc9713158d1db1a Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Fri, 15 Nov 2024 00:20:11 -0500 Subject: [PATCH 22/61] update docs --- docs-website/docs/isograph-config.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs-website/docs/isograph-config.md b/docs-website/docs/isograph-config.md index dc92fe619..da4ae4394 100644 --- a/docs-website/docs/isograph-config.md +++ b/docs-website/docs/isograph-config.md @@ -6,7 +6,7 @@ The file should be named `isograph.config.json` and located at the root of your ## Config file contents -An example (complete) Isograph config is as follows: +An example (complete) Isograph config is as follows. It contains default `options`: ```json { @@ -15,7 +15,9 @@ An example (complete) Isograph config is as follows: "schema": "./backend/schema.graphql", "schema_extensions": ["./backend/schema-extension.graphql"], "options": { - "on_invalid_id_type": "error" + "on_invalid_id_type": "error", + "on_missing_babel_transform": "error", + "include_file_extensions_in_import_statements": false } } ``` From a14e85e5df2886d08e6532cea91d39d780b0d6c1 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Fri, 15 Nov 2024 00:21:45 -0500 Subject: [PATCH 23/61] update workflow --- docs-website/docs/workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-website/docs/workflow.md b/docs-website/docs/workflow.md index a46032135..f83436622 100644 --- a/docs-website/docs/workflow.md +++ b/docs-website/docs/workflow.md @@ -18,7 +18,7 @@ A babel plugin transforms your `iso` literals. For example, `iso` entrypoint lit ## Debugging - You can use React dev tools. In particular, searching for `@component` will show you where all of your resolvers defined with `@component` are rendered. -- If you call `registerLogFunction` on your environment, you can get additional logs. +- The environment constructor takes an optional logger. Pass `console.log` to have Isograph print out everything it is doing, if you're curious! ## Workflow for modifying Isograph From 2732341087d698aa6d2052f2705eb8fe4ab2f64a Mon Sep 17 00:00:00 2001 From: Lane Sawyer Date: Thu, 14 Nov 2024 21:46:43 -0800 Subject: [PATCH 24/61] fix: Add noUncheckedIndexAccess to tsconfig (#281) # fix: Add noUncheckedIndexAccess to tsconfig ## What - Adds the `noUncheckedIndexAccess` option to the base `tsconfig.json` file - Fixes the errors that appeared - Resolves #213 ## How - Added line to the file - Fixed issues primarily by throwing errors, as each area of the code expects it to exist and something is very wrong if it doesn't - Loosened equality checks in some places so all falsey values are caught - Changed a few `null`s to `undefined`s because accessing an index that doesn't exist (or accessing an array with `undefined` as an index value returns `undefined`) --- libs/isograph-react/src/core/cache.ts | 2 +- libs/isograph-react/src/core/read.ts | 27 ++++++++++++++----- .../useConnectionSpecPagination.ts | 26 +++++++++++++----- .../loadable-hooks/useSkipLimitPagination.ts | 24 ++++++++++++++--- tsconfig.build.json | 1 + 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/libs/isograph-react/src/core/cache.ts b/libs/isograph-react/src/core/cache.ts index 956646eec..51f4dcc07 100644 --- a/libs/isograph-react/src/core/cache.ts +++ b/libs/isograph-react/src/core/cache.ts @@ -516,7 +516,7 @@ function normalizeLinkedField( const dataIds: (Link | null)[] = []; for (let i = 0; i < networkResponseData.length; i++) { const networkResponseObject = networkResponseData[i]; - if (networkResponseObject === null) { + if (networkResponseObject == null) { dataIds.push(null); continue; } diff --git a/libs/isograph-react/src/core/read.ts b/libs/isograph-react/src/core/read.ts index b5152a42a..60643ec71 100644 --- a/libs/isograph-react/src/core/read.ts +++ b/libs/isograph-react/src/core/read.ts @@ -342,10 +342,12 @@ function readData( }; } else { const refetchQueryIndex = field.refetchQuery; - if (refetchQueryIndex == null) { - throw new Error('refetchQuery is null in RefetchField'); - } const refetchQuery = nestedRefetchQueries[refetchQueryIndex]; + if (refetchQuery == null) { + throw new Error( + 'refetchQuery is null in RefetchField. This is indicative of a bug in Isograph.', + ); + } const refetchQueryArtifact = refetchQuery.artifact; const allowedVariables = refetchQuery.allowedVariables; @@ -371,9 +373,15 @@ function readData( } case 'Resolver': { const usedRefetchQueries = field.usedRefetchQueries; - const resolverRefetchQueries = usedRefetchQueries.map( - (index) => nestedRefetchQueries[index], - ); + const resolverRefetchQueries = usedRefetchQueries.map((index) => { + const resolverRefetchQuery = nestedRefetchQueries[index]; + if (resolverRefetchQuery == null) { + throw new Error( + 'resolverRefetchQuery is null in Resolver. This is indicative of a bug in Isograph.', + ); + } + return resolverRefetchQuery; + }); switch (field.readerArtifact.kind) { case 'EagerReaderArtifact': { @@ -636,7 +644,12 @@ function generateChildVariableMap( const childVars: Writable = {}; for (const [name, value] of fieldArguments) { if (value.kind === 'Variable') { - childVars[name] = variables[value.name]; + const variable = variables[value.name]; + // Variable could be null if it was not provided but has a default case, + // so we allow the loop to continue rather than throwing an error. + if (variable != null) { + childVars[name] = variable; + } } else { childVars[name] = value.value; } diff --git a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts index 9c6723698..7d39537eb 100644 --- a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts @@ -114,8 +114,16 @@ export function useConnectionSpecPagination< fragmentReference.readerWithRefetchQueries, ); + // invariant: readOutDataAndRecords.length === completedReferences.length + const data = readOutDataAndRecords[i]?.item; + if (data == null) { + throw new Error( + 'Parameter data is unexpectedly null. This is indicative of a bug in Isograph.', + ); + } + const firstParameter = { - data: readOutDataAndRecords[i].item, + data, parameters: fragmentReference.variables, }; @@ -165,10 +173,17 @@ export function useConnectionSpecPagination< fragmentReference.readerWithRefetchQueries, ); + const records = readOutDataAndRecords[i]; + if (records == null) { + throw new Error( + 'subscribeCompletedFragmentReferences records is unexpectedly null', + ); + } + return { fragmentReference, readerAst: readerWithRefetchQueries.readerArtifact.readerAst, - records: readOutDataAndRecords[i], + records, callback(_data) { rerender({}); }, @@ -224,10 +239,9 @@ export function useConnectionSpecPagination< const loadedReferences = state === UNASSIGNED_STATE ? [] : state; - const mostRecentItem: LoadedFragmentReference< - TReadFromStore, - Connection - > | null = loadedReferences[loadedReferences.length - 1]; + const mostRecentItem: + | LoadedFragmentReference> + | undefined = loadedReferences[loadedReferences.length - 1]; const mostRecentFragmentReference = mostRecentItem?.[0].getItemIfNotDisposed(); diff --git a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts index 46a077fba..07d29a9a9 100644 --- a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts @@ -106,8 +106,16 @@ export function useSkipLimitPagination< fragmentReference.readerWithRefetchQueries, ); + // invariant: readOutDataAndRecords.length === completedReferences.length + const data = readOutDataAndRecords[i]?.item; + if (data == null) { + throw new Error( + 'Parameter data is unexpectedly null. This is indicative of a bug in Isograph.', + ); + } + const firstParameter = { - data: readOutDataAndRecords[i].item, + data, parameters: fragmentReference.variables, }; @@ -150,10 +158,17 @@ export function useSkipLimitPagination< fragmentReference.readerWithRefetchQueries, ); + const records = readOutDataAndRecords[i]; + if (records == null) { + throw new Error( + 'subscribeCompletedFragmentReferences records is unexpectedly null', + ); + } + return { fragmentReference, readerAst: readerWithRefetchQueries.readerArtifact.readerAst, - records: readOutDataAndRecords[i], + records, callback(_data) { rerender({}); }, @@ -209,8 +224,9 @@ export function useSkipLimitPagination< const loadedReferences = state === UNASSIGNED_STATE ? [] : state; - const mostRecentItem: LoadedFragmentReference | null = - loadedReferences[loadedReferences.length - 1]; + const mostRecentItem: + | LoadedFragmentReference + | undefined = loadedReferences[loadedReferences.length - 1]; const mostRecentFragmentReference = mostRecentItem?.[0].getItemIfNotDisposed(); diff --git a/tsconfig.build.json b/tsconfig.build.json index 288b569dd..3769b86f5 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -15,6 +15,7 @@ "noPropertyAccessFromIndexSignature": true, "noUnusedParameters": true, "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, "paths": { "@isograph/*": ["./libs/*"] }, From a481ba3dfef6a52f62e18a5f9ed420d5ebef37e2 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Fri, 15 Nov 2024 10:19:07 -0500 Subject: [PATCH 25/61] log a message when a network response errors out --- libs/isograph-react/src/core/logging.ts | 5 +++++ libs/isograph-react/src/core/makeNetworkRequest.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/libs/isograph-react/src/core/logging.ts b/libs/isograph-react/src/core/logging.ts index 379a26092..a19610bb0 100644 --- a/libs/isograph-react/src/core/logging.ts +++ b/libs/isograph-react/src/core/logging.ts @@ -60,6 +60,11 @@ export type LogMessage = networkResponse: any; networkRequestId: string; } + | { + kind: 'ReceivedNetworkError'; + error: any; + networkRequestId: string; + } | { kind: 'MissingFieldHandlerCalled'; root: Link; diff --git a/libs/isograph-react/src/core/makeNetworkRequest.ts b/libs/isograph-react/src/core/makeNetworkRequest.ts index 0aa2f3040..0b47c77a2 100644 --- a/libs/isograph-react/src/core/makeNetworkRequest.ts +++ b/libs/isograph-react/src/core/makeNetworkRequest.ts @@ -129,6 +129,11 @@ export function makeNetworkRequest( } catch {} }) .catch((e) => { + logMessage(environment, { + kind: 'ReceivedNetworkError', + networkRequestId: myNetworkRequestId, + error: e, + }); try { fetchOptions?.onError?.(); } catch {} From 732b85d7b651d723edd4a2d73bde6135d8b55478 Mon Sep 17 00:00:00 2001 From: Sami Syed <91293085+saminoorsyed@users.noreply.github.com> Date: Mon, 18 Nov 2024 21:54:05 -0500 Subject: [PATCH 26/61] adjusts vsCode extension to use npm instead of pnpm (#283) --- vscode-extension/package-lock.json | 12 +++++++----- vscode-extension/package.json | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json index 4c134cd96..99703648d 100644 --- a/vscode-extension/package-lock.json +++ b/vscode-extension/package-lock.json @@ -25,9 +25,10 @@ "eslint-config-airbnb-typescript": "^17.0.0", "eslint-plugin-import": "^2.26.0", "prettier": "^2.6.2", - "typescript": "^4.6.3" + "typescript": "5.6.3" }, "engines": { + "node": "22.9.0", "vscode": "^1.60.0" } }, @@ -5207,16 +5208,17 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uc.micro": { diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 7521d0fde..7a612bc8a 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -65,11 +65,11 @@ "prettier-check": "prettier -c .", "prettier-write": "prettier --write .", "lint": "eslint --max-warnings 0 .", - "vscode:prepublish": "rm -f tsconfig.tsbuildinfo && rm -rf out && pnpm run esbuild-base -- --minify", + "vscode:prepublish": "rm -f tsconfig.tsbuildinfo && rm -rf out && npm run esbuild-base -- --minify", "build-local": "vsce package", "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/extension.js --external:vscode --format=cjs --platform=node", - "esbuild": "pnpm run esbuild-base --sourcemap", - "esbuild-watch": "pnpm run esbuild-base --sourcemap --watch" + "esbuild": "npm run esbuild-base --sourcemap", + "esbuild-watch": "npm run esbuild-base --sourcemap --watch" }, "engines": { "node": "22.9.0", From 20ded20a65184d85feb9ecefccf4104d8eced819 Mon Sep 17 00:00:00 2001 From: Sami Syed <91293085+saminoorsyed@users.noreply.github.com> Date: Mon, 18 Nov 2024 22:32:56 -0500 Subject: [PATCH 27/61] update dev workflow doc to reflect VSCode extension setup (#284) --- docs-website/docs/development-workflow.md | 27 ++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/docs-website/docs/development-workflow.md b/docs-website/docs/development-workflow.md index 8d0738e31..b49405670 100644 --- a/docs-website/docs/development-workflow.md +++ b/docs-website/docs/development-workflow.md @@ -16,7 +16,7 @@ The node.js and pnpm versions used by Isograph are specified in fields `engines. In order to ensure you are using the correct versions of these you should install `fnm` for your respective operating system by following [this](https://github.com/Schniz/fnm?tab=readme-ov-file#installation) guide. Optionally, configure fnm for your shell by following [this](https://github.com/Schniz/fnm?tab=readme-ov-file#shell-setup) guide. -Now, navigate to the root directory of your Isograph repository and run the following commands one by one: +Now, navigate to the root directory of your Isograph repository and run the following commands: ```bash fnm install --resolve-engines @@ -111,7 +111,7 @@ pnpm i ### Build the Isograph JavaScript libraries for use in demos ```sh -pnpm -r watch-libs +pnpm watch-libs ``` `watch-libs` will watch the source files for changes, and rebuild everything. If you only want to do it once, you can: @@ -152,8 +152,29 @@ pnpm backend ### Starting +- If you haven't yet, build the Isograph javascript libraries for the demos by running the following from root: + +```sh +pnpm i +pnpm watch-libs +``` + +or + +```sh +pnpm i +pnpm -r compile +``` + - Open VSCode in `isograph/vscode-extension` -- Open the "run and debug" sidebar, and click `Run and Debug`. If given a choice, select something related to "Extension development host". +- Run the following in `isograph/vscode-extension`: + +```sh +npm i +npm run build-local +``` + +- Open `src/extension.ts` in your editor, then open the "run and debug" sidebar and click `Run and Debug`. If given a choice, select something related to "Extension development host". - In this new window, open `isograph/demos/pet-demo`. - The VSCode extension should start when you open a JS, JSX, TS or TSX file. From b39e4ef2e40564220a71fa5dc07d1aa5172f40ef Mon Sep 17 00:00:00 2001 From: Vadim Evseev Date: Fri, 22 Nov 2024 07:48:19 +0300 Subject: [PATCH 28/61] replace std::Error with String (#285) --- crates/isograph_compiler/src/batch_compile.rs | 14 ++++---------- .../isograph_compiler/src/isograph_literals.rs | 8 +++++--- crates/isograph_compiler/src/schema.rs | 8 ++++---- crates/isograph_compiler/src/source_files.rs | 4 ++-- .../isograph_compiler/src/write_artifacts.rs | 18 +++++++++--------- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/crates/isograph_compiler/src/batch_compile.rs b/crates/isograph_compiler/src/batch_compile.rs index 8a11ebeb0..dd2ee3c40 100644 --- a/crates/isograph_compiler/src/batch_compile.rs +++ b/crates/isograph_compiler/src/batch_compile.rs @@ -59,10 +59,7 @@ pub fn print_result( #[derive(Error, Debug)] pub enum BatchCompileError { #[error("Unable to load schema file at path {path:?}.\nReason: {message}")] - UnableToLoadSchema { - path: PathBuf, - message: std::io::Error, - }, + UnableToLoadSchema { path: PathBuf, message: String }, #[error("Attempted to load the graphql schema at the following path: {path:?}, but that is not a file.")] SchemaNotAFile { path: PathBuf }, @@ -74,13 +71,10 @@ pub enum BatchCompileError { ProjectRootNotADirectory { path: PathBuf }, #[error("Unable to read the file at the following path: {path:?}.\nReason: {message}")] - UnableToReadFile { - path: PathBuf, - message: std::io::Error, - }, + UnableToReadFile { path: PathBuf, message: String }, - #[error("Unable to traverse directory.\nReason: {0}")] - UnableToTraverseDirectory(#[from] std::io::Error), + #[error("Unable to traverse directory.\nReason: {message}")] + UnableToTraverseDirectory { message: String }, #[error("Unable to parse schema.\n\n{0}")] UnableToParseSchema(#[from] WithLocation), diff --git a/crates/isograph_compiler/src/isograph_literals.rs b/crates/isograph_compiler/src/isograph_literals.rs index ef9dd78c8..2171b39f4 100644 --- a/crates/isograph_compiler/src/isograph_literals.rs +++ b/crates/isograph_compiler/src/isograph_literals.rs @@ -56,9 +56,9 @@ pub fn read_file( let path_2 = path.clone(); // N.B. we have previously ensured that path is a file - let contents = std::fs::read(&path).map_err(|message| BatchCompileError::UnableToReadFile { + let contents = std::fs::read(&path).map_err(|e| BatchCompileError::UnableToReadFile { path: path_2, - message, + message: e.to_string(), })?; let contents = std::str::from_utf8(&contents) @@ -80,7 +80,9 @@ fn read_dir_recursive(root_js_path: &Path) -> Result, BatchCompileE visit_dirs_skipping_isograph(root_js_path, &mut |dir_entry| { paths.push(dir_entry.path()); }) - .map_err(BatchCompileError::from)?; + .map_err(|e| BatchCompileError::UnableToTraverseDirectory { + message: e.to_string(), + })?; Ok(paths) } diff --git a/crates/isograph_compiler/src/schema.rs b/crates/isograph_compiler/src/schema.rs index 17ef38bf7..4b58d8fd1 100644 --- a/crates/isograph_compiler/src/schema.rs +++ b/crates/isograph_compiler/src/schema.rs @@ -9,9 +9,9 @@ pub(crate) fn read_schema_file(path: &PathBuf) -> Result Result Result Date: Fri, 22 Nov 2024 20:33:29 -0500 Subject: [PATCH 29/61] update quickstart to note latest main --- docs-website/docs/quickstart.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs-website/docs/quickstart.md b/docs-website/docs/quickstart.md index 25dabdc60..fc6b8c122 100644 --- a/docs-website/docs/quickstart.md +++ b/docs-website/docs/quickstart.md @@ -32,6 +32,10 @@ yarn add --dev @isograph/babel-plugin yarn add @isograph/react ``` +:::tip +For each of these packages, you should install the latest `main` version, which can be found [here](https://www.npmjs.com/package/@isograph/react?activeTab=versions). So e.g. `yarn add --dev @isograph/compiler@0.0.0-main-c8c7d9f2`. Lots of great features have shipped since we cut a release! +::: + Installing the compiler also adds the command `yarn iso` and `yarn iso --watch`. But hang tight — before this command works, you'll need to create a folder, download your schema and create an `isograph.config.json` file! ## Create an `isograph.config.json` From 07c645fb9a2292c3fd16cf3670273014571f301a Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Tue, 3 Dec 2024 12:31:44 -0800 Subject: [PATCH 30/61] do not specify dependency version for @isograph/disposable-types in isograph-react --- libs/isograph-react-disposable-state/package.json | 2 +- libs/isograph-react/package.json | 2 +- libs/isograph-reference-counted-pointer/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/isograph-react-disposable-state/package.json b/libs/isograph-react-disposable-state/package.json index c0d5f819c..8e21681f5 100644 --- a/libs/isograph-react-disposable-state/package.json +++ b/libs/isograph-react-disposable-state/package.json @@ -18,7 +18,7 @@ "tsc": "tsc" }, "dependencies": { - "@isograph/disposable-types": "0.2.0" + "@isograph/disposable-types": "*" }, "peerDependencies": { "react": "18.3.1" diff --git a/libs/isograph-react/package.json b/libs/isograph-react/package.json index eadbf2dfa..382abccd2 100644 --- a/libs/isograph-react/package.json +++ b/libs/isograph-react/package.json @@ -19,7 +19,7 @@ "iso-watch": "../../target/debug/isograph_cli --config ./isograph.config.json --watch" }, "dependencies": { - "@isograph/disposable-types": "0.2.0", + "@isograph/disposable-types": "*", "@isograph/react-disposable-state": "*", "@isograph/reference-counted-pointer": "*" }, diff --git a/libs/isograph-reference-counted-pointer/package.json b/libs/isograph-reference-counted-pointer/package.json index ae12d900f..56966e48d 100644 --- a/libs/isograph-reference-counted-pointer/package.json +++ b/libs/isograph-reference-counted-pointer/package.json @@ -16,7 +16,7 @@ "tsc": "tsc" }, "dependencies": { - "@isograph/disposable-types": "0.2.0" + "@isograph/disposable-types": "*" }, "devDependencies": { "typescript": "5.6.3" From 19bc177a0e6b24b755903686b05a0051befcf7f6 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Tue, 3 Dec 2024 12:33:40 -0800 Subject: [PATCH 31/61] v0.3.0 --- Cargo.lock | 28 +++++++++---------- Cargo.toml | 6 ++-- demos/disposable-state-ajax-demo/package.json | 2 +- demos/github-demo/package.json | 2 +- demos/pet-demo/backend/package.json | 2 +- demos/pet-demo/package.json | 2 +- docs-website/docs/development-workflow.md | 1 + libs/isograph-babel-plugin/package.json | 2 +- libs/isograph-compiler/package.json | 2 +- libs/isograph-disposable-types/package.json | 2 +- .../package.json | 2 +- libs/isograph-react/package.json | 2 +- .../package.json | 2 +- pnpm-lock.yaml | 6 ++-- 14 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2cb8ccfc..a3fc1812c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,7 +306,7 @@ dependencies = [ [[package]] name = "common_lang_types" -version = "0.2.0" +version = "0.3.0" dependencies = [ "intern", "serde", @@ -535,7 +535,7 @@ dependencies = [ [[package]] name = "graphql_artifact_generation" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql_lang_types", @@ -549,7 +549,7 @@ dependencies = [ [[package]] name = "graphql_lang_types" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "intern", @@ -560,7 +560,7 @@ dependencies = [ [[package]] name = "graphql_schema_parser" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql-syntax", @@ -724,7 +724,7 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "isograph_cli" -version = "0.2.0" +version = "0.3.0" dependencies = [ "clap 4.5.18", "colored", @@ -744,7 +744,7 @@ dependencies = [ [[package]] name = "isograph_compiler" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colored", "common_lang_types", @@ -768,7 +768,7 @@ dependencies = [ [[package]] name = "isograph_config" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colorize", "common_lang_types", @@ -779,7 +779,7 @@ dependencies = [ [[package]] name = "isograph_lang_parser" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql_lang_types", @@ -791,7 +791,7 @@ dependencies = [ [[package]] name = "isograph_lang_types" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "graphql_lang_types", @@ -803,7 +803,7 @@ dependencies = [ [[package]] name = "isograph_lsp" -version = "0.2.0" +version = "0.3.0" dependencies = [ "common_lang_types", "crossbeam", @@ -822,7 +822,7 @@ dependencies = [ [[package]] name = "isograph_schema" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colorize", "common_lang_types", @@ -1420,7 +1420,7 @@ dependencies = [ [[package]] name = "string_key_newtype" -version = "0.2.0" +version = "0.3.0" dependencies = [ "intern", ] @@ -1580,7 +1580,7 @@ dependencies = [ [[package]] name = "tests" -version = "0.2.0" +version = "0.3.0" dependencies = [ "colorize", "common_lang_types", @@ -1775,7 +1775,7 @@ dependencies = [ [[package]] name = "u32_newtypes" -version = "0.2.0" +version = "0.3.0" [[package]] name = "unicase" diff --git a/Cargo.toml b/Cargo.toml index a5bde2073..103d190ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["./crates/*"] resolver = "2" [workspace.package] -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" @@ -24,8 +24,8 @@ pretty-duration = "0.1.1" regex = "1.6.0" serde = "1.0.197" serde_json = "1.0.108" -strum = { version="0.25.0", features=["derive"] } +strum = { version = "0.25.0", features = ["derive"] } thiserror = "1.0.40" -tokio = { version="1.35.0", features=["full"] } +tokio = { version = "1.35.0", features = ["full"] } tracing = "0.1" tracing-subscriber = "0.3" diff --git a/demos/disposable-state-ajax-demo/package.json b/demos/disposable-state-ajax-demo/package.json index ef6abb3f1..1cae26350 100644 --- a/demos/disposable-state-ajax-demo/package.json +++ b/demos/disposable-state-ajax-demo/package.json @@ -1,6 +1,6 @@ { "name": "disposable-state-ajax-demo", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", diff --git a/demos/github-demo/package.json b/demos/github-demo/package.json index 82073c2ee..84a77ddb0 100644 --- a/demos/github-demo/package.json +++ b/demos/github-demo/package.json @@ -1,6 +1,6 @@ { "name": "github-demo", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", diff --git a/demos/pet-demo/backend/package.json b/demos/pet-demo/backend/package.json index 638cfd794..4ee93fc4c 100644 --- a/demos/pet-demo/backend/package.json +++ b/demos/pet-demo/backend/package.json @@ -1,6 +1,6 @@ { "name": "pet-demo-backend", - "version": "0.2.0", + "version": "0.3.0", "private": true, "type": "module", "dependencies": { diff --git a/demos/pet-demo/package.json b/demos/pet-demo/package.json index abb928b69..6da5c272c 100644 --- a/demos/pet-demo/package.json +++ b/demos/pet-demo/package.json @@ -1,6 +1,6 @@ { "name": "pet-demo", - "version": "0.2.0", + "version": "0.3.0", "private": true, "scripts": { "dev": "next dev", diff --git a/docs-website/docs/development-workflow.md b/docs-website/docs/development-workflow.md index b49405670..5e3e0a321 100644 --- a/docs-website/docs/development-workflow.md +++ b/docs-website/docs/development-workflow.md @@ -194,6 +194,7 @@ Every commit to `main` results in a build, which you can see in [npm](https://ww ## How to release a new "numbered" version of Isograph to npm - In all package.json files, bump the version number. Don't forget to bump the version number of imports. +- `pnpm i` - `git add . && git commit -m 'v0.1.0' && git tag v0.1.0 && git push` - See [this commit releasing 0.2.0](https://github.com/isographlabs/isograph/commit/e36acab1a018e18bdae0558be08952693af3b6a8) diff --git a/libs/isograph-babel-plugin/package.json b/libs/isograph-babel-plugin/package.json index 0aca74dfd..364f4bebb 100644 --- a/libs/isograph-babel-plugin/package.json +++ b/libs/isograph-babel-plugin/package.json @@ -2,7 +2,7 @@ "name": "@isograph/babel-plugin", "description": "A Babel plugin for use with Isograph applications.", "homepage": "https://isograph.dev", - "version": "0.2.0", + "version": "0.3.0", "keywords": [ "graphql", "isograph", diff --git a/libs/isograph-compiler/package.json b/libs/isograph-compiler/package.json index ca8515191..8b61eabef 100644 --- a/libs/isograph-compiler/package.json +++ b/libs/isograph-compiler/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/compiler", - "version": "0.2.0", + "version": "0.3.0", "description": "Installs the `yarn iso` command, which runs the Isograph compiler.", "homepage": "https://isograph.dev", "main": "index.js", diff --git a/libs/isograph-disposable-types/package.json b/libs/isograph-disposable-types/package.json index 6222efb69..26352bed8 100644 --- a/libs/isograph-disposable-types/package.json +++ b/libs/isograph-disposable-types/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/disposable-types", - "version": "0.2.0", + "version": "0.3.0", "description": "Common types for disposable items", "homepage": "https://isograph.dev", "main": "dist/index.js", diff --git a/libs/isograph-react-disposable-state/package.json b/libs/isograph-react-disposable-state/package.json index 8e21681f5..06f831afe 100644 --- a/libs/isograph-react-disposable-state/package.json +++ b/libs/isograph-react-disposable-state/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/react-disposable-state", - "version": "0.2.0", + "version": "0.3.0", "description": "Primitives for managing disposable state in React", "homepage": "https://isograph.dev", "main": "dist/index.js", diff --git a/libs/isograph-react/package.json b/libs/isograph-react/package.json index 382abccd2..7db940494 100644 --- a/libs/isograph-react/package.json +++ b/libs/isograph-react/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/react", - "version": "0.2.0", + "version": "0.3.0", "description": "Use Isograph with React", "homepage": "https://isograph.dev", "main": "dist/index.js", diff --git a/libs/isograph-reference-counted-pointer/package.json b/libs/isograph-reference-counted-pointer/package.json index 56966e48d..a3f1b2bb4 100644 --- a/libs/isograph-reference-counted-pointer/package.json +++ b/libs/isograph-reference-counted-pointer/package.json @@ -1,6 +1,6 @@ { "name": "@isograph/reference-counted-pointer", - "version": "0.2.0", + "version": "0.3.0", "description": "Reference counted pointers enable sharing of disposable items.", "homepage": "https://isograph.dev", "main": "dist/index.js", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98e0178d4..e79f9f65f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -321,7 +321,7 @@ importers: libs/isograph-react: dependencies: '@isograph/disposable-types': - specifier: 0.2.0 + specifier: '*' version: link:../isograph-disposable-types '@isograph/react-disposable-state': specifier: '*' @@ -355,7 +355,7 @@ importers: libs/isograph-react-disposable-state: dependencies: '@isograph/disposable-types': - specifier: 0.2.0 + specifier: '*' version: link:../isograph-disposable-types react: specifier: 18.3.1 @@ -377,7 +377,7 @@ importers: libs/isograph-reference-counted-pointer: dependencies: '@isograph/disposable-types': - specifier: 0.2.0 + specifier: '*' version: link:../isograph-disposable-types devDependencies: typescript: From f8daaecd10ccdaa57fe3613a1dea04d3cfbc60ef Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Tue, 3 Dec 2024 17:04:52 -0800 Subject: [PATCH 32/61] remove unused import --- demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx index 29eebd7df..aff1000d6 100644 --- a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx +++ b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx @@ -1,4 +1,4 @@ -import React, { Suspense, useState } from 'react'; +import React, { Suspense } from 'react'; import { iso } from '@iso'; import { Card, CardContent, Container, Stack, Typography } from '@mui/material'; import { From 7cb5bb62c07bbef89c32141d809c6bf75f0eb954 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 4 Dec 2024 02:47:11 -0800 Subject: [PATCH 33/61] remove unused import --- demos/github-demo/src/isograph-components/HomeRoute.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/github-demo/src/isograph-components/HomeRoute.tsx b/demos/github-demo/src/isograph-components/HomeRoute.tsx index 7e262a3b4..4fe946452 100644 --- a/demos/github-demo/src/isograph-components/HomeRoute.tsx +++ b/demos/github-demo/src/isograph-components/HomeRoute.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useResult, useLazyReference, subscribe } from '@isograph/react'; +import { useResult, useLazyReference } from '@isograph/react'; import { iso } from '@iso'; import { Container } from '@mui/material'; From 7648a87ac22c04b44e970005716300ff9e7ecf26 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 4 Dec 2024 03:35:43 -0800 Subject: [PATCH 34/61] onComplete takes a parameter (TClientFieldValue aka TResult) - in makeNetworkRequest, if we pass an onComplete, we read out the result (after normalizing the data.) This gets passed to the resolver (for eager resolvers) or to getOrCreateCachedComponent (for @component resolvers). That value gets passed to onComplete - FetchOptions, as a result, must be generic, and that must be threaded through everywhere Further improvements: - makeNetworkRequest and read are doing some pretty redundant work... those should be combined - if reading the read-out value is expensive, then there should be two onComplete's: onCompleteWithReadOutValue and onComplete. However, this can be added later (if necessary) --- .../src/components/FavoritePhrase.tsx | 4 +- .../src/components/PetDetailRoute.tsx | 7 +- libs/isograph-react/src/core/cache.ts | 2 +- libs/isograph-react/src/core/check.ts | 4 +- .../src/core/makeNetworkRequest.ts | 135 ++++++++++++++++-- libs/isograph-react/src/core/read.ts | 7 +- libs/isograph-react/src/core/reader.ts | 2 +- .../src/loadable-hooks/useClientSideDefer.ts | 6 +- .../useConnectionSpecPagination.ts | 7 +- .../useImperativeLoadableField.ts | 4 +- .../loadable-hooks/useSkipLimitPagination.ts | 10 +- .../src/react/useImperativeReference.ts | 4 +- .../src/react/useLazyReference.ts | 2 +- 13 files changed, 159 insertions(+), 35 deletions(-) diff --git a/demos/pet-demo/src/components/FavoritePhrase.tsx b/demos/pet-demo/src/components/FavoritePhrase.tsx index 0e005c86e..610909342 100644 --- a/demos/pet-demo/src/components/FavoritePhrase.tsx +++ b/demos/pet-demo/src/components/FavoritePhrase.tsx @@ -21,8 +21,8 @@ export const FavoritePhraseLoader = iso(` loadFragmentReference( { id: pet.id }, { - onComplete: () => { - console.log('Successfully loaded favorite phrase'); + onComplete: (data) => { + console.log('Successfully loaded favorite phrase', data); }, onError: () => { console.log('Error when loading favorite phrase'); diff --git a/demos/pet-demo/src/components/PetDetailRoute.tsx b/demos/pet-demo/src/components/PetDetailRoute.tsx index 1e4af6715..abc16c4f2 100644 --- a/demos/pet-demo/src/components/PetDetailRoute.tsx +++ b/demos/pet-demo/src/components/PetDetailRoute.tsx @@ -54,8 +54,11 @@ export function PetDetailRouteLoader({ route }: { route: PetDetailRoute }) { iso(`entrypoint Query.PetDetailRoute`), { id: route.id }, { - onComplete: () => { - console.log('The Query.PetDetailRoute network request has completed.'); + onComplete: (data) => { + console.log( + 'The Query.PetDetailRoute network request has completed.', + data, + ); }, onError: () => { console.log('The Query.PetDetailRoute network request errored out.'); diff --git a/libs/isograph-react/src/core/cache.ts b/libs/isograph-react/src/core/cache.ts index 51f4dcc07..088331e53 100644 --- a/libs/isograph-react/src/core/cache.ts +++ b/libs/isograph-react/src/core/cache.ts @@ -89,7 +89,7 @@ export function getOrCreateCacheForArtifact< environment: IsographEnvironment, entrypoint: IsographEntrypoint, variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): ParentCache> { const cacheKey = entrypoint.networkRequestInfo.queryText + diff --git a/libs/isograph-react/src/core/check.ts b/libs/isograph-react/src/core/check.ts index c2afbed75..1e7d0b210 100644 --- a/libs/isograph-react/src/core/check.ts +++ b/libs/isograph-react/src/core/check.ts @@ -13,9 +13,9 @@ export type ShouldFetch = 'Yes' | 'No' | 'IfNecessary'; export const DEFAULT_SHOULD_FETCH_VALUE: ShouldFetch = 'IfNecessary'; -export type FetchOptions = { +export type FetchOptions = { shouldFetch?: ShouldFetch; - onComplete?: () => void; + onComplete?: (data: TReadOutData) => void; onError?: () => void; }; diff --git a/libs/isograph-react/src/core/makeNetworkRequest.ts b/libs/isograph-react/src/core/makeNetworkRequest.ts index 0b47c77a2..18957eb24 100644 --- a/libs/isograph-react/src/core/makeNetworkRequest.ts +++ b/libs/isograph-react/src/core/makeNetworkRequest.ts @@ -3,14 +3,14 @@ import { IsographEntrypoint, RefetchQueryNormalizationArtifact, } from './entrypoint'; -import { Variables } from './FragmentReference'; +import { ExtractParameters } from './FragmentReference'; import { garbageCollectEnvironment, RetainedQuery, retainQuery, unretainQuery, } from './garbageCollection'; -import { IsographEnvironment, ROOT_ID } from './IsographEnvironment'; +import { IsographEnvironment, Link, ROOT_ID } from './IsographEnvironment'; import { AnyError, PromiseWrapper, @@ -20,14 +20,21 @@ import { import { normalizeData } from './cache'; import { logMessage } from './logging'; import { check, DEFAULT_SHOULD_FETCH_VALUE, FetchOptions } from './check'; +import { readButDoNotEvaluate } from './read'; +import { getOrCreateCachedComponent } from './componentCache'; let networkRequestId = 0; -export function maybeMakeNetworkRequest( +export function maybeMakeNetworkRequest< + TReadFromStore extends { parameters: object; data: object }, + TClientFieldValue, +>( environment: IsographEnvironment, - artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint, - variables: Variables, - fetchOptions?: FetchOptions, + artifact: + | RefetchQueryNormalizationArtifact + | IsographEntrypoint, + variables: ExtractParameters, + fetchOptions?: FetchOptions, ): ItemCleanupPair> { switch (fetchOptions?.shouldFetch ?? DEFAULT_SHOULD_FETCH_VALUE) { case 'Yes': { @@ -60,11 +67,16 @@ export function maybeMakeNetworkRequest( } } -export function makeNetworkRequest( +export function makeNetworkRequest< + TReadFromStore extends { parameters: object; data: object }, + TClientFieldValue, +>( environment: IsographEnvironment, - artifact: RefetchQueryNormalizationArtifact | IsographEntrypoint, - variables: Variables, - fetchOptions?: FetchOptions, + artifact: + | RefetchQueryNormalizationArtifact + | IsographEntrypoint, + variables: ExtractParameters, + fetchOptions?: FetchOptions, ): ItemCleanupPair> { // TODO this should be a DataId and stored in the store const myNetworkRequestId = networkRequestId + ''; @@ -100,8 +112,8 @@ export function makeNetworkRequest( }); } + const root = { __link: ROOT_ID, __typename: artifact.concreteType }; if (status.kind === 'UndisposedIncomplete') { - const root = { __link: ROOT_ID, __typename: artifact.concreteType }; normalizeData( environment, artifact.networkRequestInfo.normalizationAst, @@ -124,9 +136,23 @@ export function makeNetworkRequest( retainQuery(environment, retainedQuery); } - try { - fetchOptions?.onComplete?.(); - } catch {} + const onComplete = fetchOptions?.onComplete; + if (onComplete != null) { + let data = readDataForOnComplete( + artifact, + environment, + root, + variables, + ); + + try { + // @ts-expect-error this problem will be fixed when we remove RefetchQueryNormalizationArtifact + // (or we can fix this by having a single param of type { kind: 'Entrypoint', entrypoint, + // fetchOptions: FetchOptions } | { kind: 'RefetchQuery', refetchQuery, + // fetchOptions: FetchOptions }). + onComplete(data); + } catch {} + } }) .catch((e) => { logMessage(environment, { @@ -173,3 +199,84 @@ type NetworkRequestStatus = readonly kind: 'UndisposedComplete'; readonly retainedQuery: RetainedQuery; }; + +function readDataForOnComplete< + TReadFromStore extends { parameters: object; data: object }, + TClientFieldValue, +>( + artifact: + | RefetchQueryNormalizationArtifact + | IsographEntrypoint, + environment: IsographEnvironment, + root: Link, + variables: ExtractParameters, +): TClientFieldValue | null { + // An entrypoint, but not a RefetchQueryNormalizationArtifact, has a reader ASTs. + // So, we can only pass data to onComplete if makeNetworkRequest was passed an entrypoint. + // This is awkward, since we don't express that in the types of the parameters + // (i.e. FetchOptions could be passed, along with a RefetchQueryNormalizationArtifact). + // + // However, this isn't a big deal: RefetchQueryNormalizationArtifact is going away. + if (artifact.kind === 'Entrypoint') { + // TODO this is a smell! + const fakeNetworkRequest = wrapResolvedValue(undefined); + // TODO this is a smell — we know the network response is not in flight, + // so we don't really care! + const fakeNetworkRequestOptions = { + suspendIfInFlight: false, + throwOnNetworkError: false, + }; + + const fragmentResult = readButDoNotEvaluate( + environment, + { + kind: 'FragmentReference', + // TODO this smells. + readerWithRefetchQueries: wrapResolvedValue( + artifact.readerWithRefetchQueries, + ), + root, + variables, + networkRequest: fakeNetworkRequest, + }, + fakeNetworkRequestOptions, + ).item; + const readerArtifact = artifact.readerWithRefetchQueries.readerArtifact; + switch (readerArtifact.kind) { + case 'ComponentReaderArtifact': { + // @ts-expect-error We should find a way to encode this in the type system: + // if we have a ComponentReaderArtifact, we will necessarily have a + // TClientFieldValue which is a React.FC<...> + return getOrCreateCachedComponent( + environment, + readerArtifact.componentName, + { + kind: 'FragmentReference', + readerWithRefetchQueries: wrapResolvedValue({ + kind: 'ReaderWithRefetchQueries', + readerArtifact: readerArtifact, + nestedRefetchQueries: + artifact.readerWithRefetchQueries.nestedRefetchQueries, + }), + root, + variables, + networkRequest: fakeNetworkRequest, + } as const, + fakeNetworkRequestOptions, + ); + } + case 'EagerReaderArtifact': { + return readerArtifact.resolver({ + data: fragmentResult, + parameters: variables, + }); + } + default: { + const _: never = readerArtifact; + _; + throw new Error('Expected case'); + } + } + } + return null; +} diff --git a/libs/isograph-react/src/core/read.ts b/libs/isograph-react/src/core/read.ts index 60643ec71..2ccb9ecf2 100644 --- a/libs/isograph-react/src/core/read.ts +++ b/libs/isograph-react/src/core/read.ts @@ -49,6 +49,7 @@ export function readButDoNotEvaluate< ): WithEncounteredRecords { const mutableEncounteredRecords: EncounteredIds = new Map(); + // TODO consider moving this to the outside const readerWithRefetchQueries = readPromise( fragmentReference.readerWithRefetchQueries, ); @@ -461,7 +462,11 @@ function readData( recordLink: refetchReaderParams.recordLink, }; } else { - target[field.alias] = (args: any, fetchOptions?: FetchOptions) => { + target[field.alias] = ( + args: any, + // TODO get the associated type for FetchOptions from the loadably selected field + fetchOptions?: FetchOptions, + ) => { // TODO we should use the reader AST for this const includeReadOutData = (variables: any, readOutData: any) => { variables.id = readOutData.id; diff --git a/libs/isograph-react/src/core/reader.ts b/libs/isograph-react/src/core/reader.ts index a36e0655f..41ff0abd5 100644 --- a/libs/isograph-react/src/core/reader.ts +++ b/libs/isograph-react/src/core/reader.ts @@ -156,5 +156,5 @@ export type LoadableField< // user-facing API. Users should only interact with LoadableFields via APIs // like useClientSideDefer. These APIs should have a nullable fetchOptions // parameter, and provide a default value ({}) to the LoadableField. - fetchOptions: FetchOptions, + fetchOptions: FetchOptions, ) => [StableId, Factory>]; diff --git a/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts b/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts index 439b70143..86c80063f 100644 --- a/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts +++ b/libs/isograph-react/src/loadable-hooks/useClientSideDefer.ts @@ -18,7 +18,7 @@ export function useClientSideDefer< ExtractParameters >, args?: Record, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference }; export function useClientSideDefer< @@ -32,7 +32,7 @@ export function useClientSideDefer< Omit, keyof TProvidedArgs> >, args: Omit, keyof TProvidedArgs>, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference }; export function useClientSideDefer< @@ -46,7 +46,7 @@ export function useClientSideDefer< Omit, keyof TProvidedArgs> >, args?: Omit, keyof TProvidedArgs>, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference } { const [id, loader] = loadableField(args, fetchOptions ?? {}); const environment = useIsographEnvironment(); diff --git a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts index 7d39537eb..92a839943 100644 --- a/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useConnectionSpecPagination.ts @@ -32,7 +32,10 @@ type UsePaginationReturnValue< } | { kind: 'Complete'; - fetchMore: (count: number, fetchOptions?: FetchOptions) => void; + fetchMore: ( + count: number, + fetchOptions?: FetchOptions>, + ) => void; results: ReadonlyArray; hasNextPage: boolean; }; @@ -194,7 +197,7 @@ export function useConnectionSpecPagination< const getFetchMore = (after: string | null) => - (count: number, fetchOptions?: FetchOptions): void => { + (count: number, fetchOptions?: FetchOptions>): void => { const loadedField = loadableField( { after: after, diff --git a/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts b/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts index 70a7bbff5..4797400d9 100644 --- a/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts +++ b/libs/isograph-react/src/loadable-hooks/useImperativeLoadableField.ts @@ -21,7 +21,7 @@ type UseImperativeLoadableFieldReturn< // TODO this should be void iff all args are provided by the query, like in // useClientSideDefer. args: Omit, keyof TProvidedArgs> | void, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => void; }; @@ -42,7 +42,7 @@ export function useImperativeLoadableField< return { loadField: ( args: Omit, keyof TProvidedArgs> | void, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => { const [_id, loader] = loadableField(args, fetchOptions ?? {}); setState(loader()); diff --git a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts index 07d29a9a9..4862bd6e2 100644 --- a/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts +++ b/libs/isograph-react/src/loadable-hooks/useSkipLimitPagination.ts @@ -25,7 +25,10 @@ type UseSkipLimitReturnValue< > = | { readonly kind: 'Complete'; - readonly fetchMore: (count: number, fetchOptions?: FetchOptions) => void; + readonly fetchMore: ( + count: number, + fetchOptions?: FetchOptions>, + ) => void; readonly results: ReadonlyArray; } | { @@ -179,7 +182,10 @@ export function useSkipLimitPagination< const getFetchMore = (loadedSoFar: number) => - (count: number, fetchOptions?: FetchOptions): void => { + ( + count: number, + fetchOptions?: FetchOptions>, + ): void => { const loadedField = loadableField( { skip: loadedSoFar, diff --git a/libs/isograph-react/src/react/useImperativeReference.ts b/libs/isograph-react/src/react/useImperativeReference.ts index 09e6d0aaa..a8f9897dd 100644 --- a/libs/isograph-react/src/react/useImperativeReference.ts +++ b/libs/isograph-react/src/react/useImperativeReference.ts @@ -26,7 +26,7 @@ export function useImperativeReference< | UnassignedState; loadFragmentReference: ( variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => void; } { const { state, setState } = @@ -38,7 +38,7 @@ export function useImperativeReference< fragmentReference: state, loadFragmentReference: ( variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ) => { const [networkRequest, disposeNetworkRequest] = maybeMakeNetworkRequest( environment, diff --git a/libs/isograph-react/src/react/useLazyReference.ts b/libs/isograph-react/src/react/useLazyReference.ts index d9af76a4c..9af3bc02c 100644 --- a/libs/isograph-react/src/react/useLazyReference.ts +++ b/libs/isograph-react/src/react/useLazyReference.ts @@ -15,7 +15,7 @@ export function useLazyReference< >( entrypoint: IsographEntrypoint, variables: ExtractParameters, - fetchOptions?: FetchOptions, + fetchOptions?: FetchOptions, ): { fragmentReference: FragmentReference; } { From 8ce4f4ecf3afd1870176bd16d047326455d549fa Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 4 Dec 2024 03:51:37 -0800 Subject: [PATCH 35/61] remove unused imports --- demos/github-demo/src/isograph-components/GithubDemo.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demos/github-demo/src/isograph-components/GithubDemo.tsx b/demos/github-demo/src/isograph-components/GithubDemo.tsx index 2a71baefc..2c75d34a0 100644 --- a/demos/github-demo/src/isograph-components/GithubDemo.tsx +++ b/demos/github-demo/src/isograph-components/GithubDemo.tsx @@ -77,7 +77,8 @@ function Router({ case 'PullRequest': return ; default: - const _exhaustiveCheck: never = route; + const _: never = route; + _; throw new Error('Unexpected route kind'); } } From b767ef875b908d2c5f8a70fb6559b8e057fa9701 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Sun, 22 Dec 2024 01:17:28 +0900 Subject: [PATCH 36/61] remove unused import --- demos/pet-demo/src/components/Newsfeed/useIntersection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx b/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx index 0f06d20e6..1e4c32847 100644 --- a/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx +++ b/demos/pet-demo/src/components/Newsfeed/useIntersection.tsx @@ -1,4 +1,4 @@ -import { Ref, useEffect, useRef } from 'react'; +import { useEffect, useRef } from 'react'; export function useOnScreen( ref: React.RefObject, From e64c08482c232bfb6f55be9b4d03a6a825339426 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Mon, 23 Dec 2024 17:45:05 +0900 Subject: [PATCH 37/61] remove unused lint suppression --- demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx index aff1000d6..c86a632ec 100644 --- a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx +++ b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx @@ -40,8 +40,6 @@ export const Newsfeed = iso(` const onVisible = index === newsfeedItems.length - 1 ? loadMore : null; return ( - // the key is not needed here! - // eslint-disable-next-line react/jsx-key Date: Mon, 23 Dec 2024 17:48:13 +0900 Subject: [PATCH 38/61] add key to newsfeed route item --- .../Newsfeed/NewsfeedPagination.tsx | 6 ++++ .../src/components/Newsfeed/NewsfeedRoute.tsx | 1 + .../NewsfeedPaginationComponent/param_type.ts | 12 +++++++ .../resolver_reader.ts | 32 +++++++++++++++++++ 4 files changed, 51 insertions(+) diff --git a/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx b/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx index 0556d47bb..bc3cbf639 100644 --- a/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx +++ b/demos/pet-demo/src/components/Newsfeed/NewsfeedPagination.tsx @@ -4,6 +4,12 @@ import { iso } from '@iso'; export const NewsfeedPaginationComponent = iso(` field Viewer.NewsfeedPaginationComponent($skip: Int!, $limit: Int!) { newsfeed(skip: $skip, limit: $limit) { + asAdItem { + id + } + asBlogItem { + id + } NewsfeedAdOrBlog } } diff --git a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx index c86a632ec..f59b5babd 100644 --- a/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx +++ b/demos/pet-demo/src/components/Newsfeed/NewsfeedRoute.tsx @@ -41,6 +41,7 @@ export const Newsfeed = iso(` index === newsfeedItems.length - 1 ? loadMore : null; return ( diff --git a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts index 6a51b8842..8559a2671 100644 --- a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts +++ b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/param_type.ts @@ -4,6 +4,18 @@ import type { Viewer__NewsfeedPaginationComponent__parameters } from './paramete export type Viewer__NewsfeedPaginationComponent__param = { readonly data: { readonly newsfeed: ReadonlyArray<{ + /** +A client pointer for the AdItem type. + */ + readonly asAdItem: ({ + readonly id: string, + } | null), + /** +A client pointer for the BlogItem type. + */ + readonly asBlogItem: ({ + readonly id: string, + } | null), readonly NewsfeedAdOrBlog: NewsfeedItem__NewsfeedAdOrBlog__output_type, }>, }, diff --git a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts index 87779da6b..018a6bb32 100644 --- a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/resolver_reader.ts @@ -2,6 +2,8 @@ import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; import { Viewer__NewsfeedPaginationComponent__param } from './param_type'; import { Viewer__NewsfeedPaginationComponent__output_type } from './output_type'; import { NewsfeedPaginationComponent as resolver } from '../../../Newsfeed/NewsfeedPagination'; +import AdItem__asAdItem__resolver_reader from '../../AdItem/asAdItem/resolver_reader'; +import BlogItem__asBlogItem__resolver_reader from '../../BlogItem/asBlogItem/resolver_reader'; import NewsfeedItem__NewsfeedAdOrBlog__resolver_reader from '../../NewsfeedItem/NewsfeedAdOrBlog/resolver_reader'; const readerAst: ReaderAst = [ @@ -22,6 +24,36 @@ const readerAst: ReaderAst = [ ], condition: null, selections: [ + { + kind: "Linked", + fieldName: "asAdItem", + alias: null, + arguments: null, + condition: AdItem__asAdItem__resolver_reader, + selections: [ + { + kind: "Scalar", + fieldName: "id", + alias: null, + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "asBlogItem", + alias: null, + arguments: null, + condition: BlogItem__asBlogItem__resolver_reader, + selections: [ + { + kind: "Scalar", + fieldName: "id", + alias: null, + arguments: null, + }, + ], + }, { kind: "Resolver", alias: "NewsfeedAdOrBlog", From 2f61c356efcee61a5cd01d88479b8938f51df1ca Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 25 Dec 2024 23:38:49 +0900 Subject: [PATCH 39/61] satisfy clippy --- relay-crates/intern/src/intern.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relay-crates/intern/src/intern.rs b/relay-crates/intern/src/intern.rs index 881ed44ed..2fe931494 100644 --- a/relay-crates/intern/src/intern.rs +++ b/relay-crates/intern/src/intern.rs @@ -242,7 +242,7 @@ impl InternTable { impl InternTable { /// The methods from here on are internal and private. - fn shards(&'static self) -> &Shards { + fn shards(&'static self) -> &'static Shards { self.shards.get_or_init(|| { let shards: Shards = ShardedSet::default(); if !self.arena.is_empty() { @@ -291,7 +291,7 @@ impl InternTable { /// Get a shared reference to the underlying `Id::Intern`. /// Usually you can rely on `deref` to do this implicitly. #[inline] - fn get(&'static self, r: Id) -> &Id::Intern { + fn get(&'static self, r: Id) -> &'static Id::Intern { &*self.arena.get(r.unwrap()) } From c0a40861c346465b6b3e9bb89f7bdb65f899eb5e Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 26 Dec 2024 00:19:26 +0900 Subject: [PATCH 40/61] satisfy clippy pt 2 --- .../graphql_artifact_generation/src/entrypoint_artifact.rs | 2 +- crates/graphql_schema_parser/src/peekable_lexer.rs | 2 +- crates/isograph_lang_parser/src/peekable_lexer.rs | 2 +- crates/isograph_lang_types/src/client_field_declaration.rs | 6 +++--- crates/isograph_schema/src/isograph_schema.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/graphql_artifact_generation/src/entrypoint_artifact.rs b/crates/graphql_artifact_generation/src/entrypoint_artifact.rs index 00406a80a..2702ec2f8 100644 --- a/crates/graphql_artifact_generation/src/entrypoint_artifact.rs +++ b/crates/graphql_artifact_generation/src/entrypoint_artifact.rs @@ -249,7 +249,7 @@ fn generate_refetch_query_artifact_import( RefetchQueryArtifactImport(output) } -impl<'schema> EntrypointArtifactInfo<'schema> { +impl EntrypointArtifactInfo<'_> { fn path_and_content( self, file_extensions: GenerateFileExtensionsOption, diff --git a/crates/graphql_schema_parser/src/peekable_lexer.rs b/crates/graphql_schema_parser/src/peekable_lexer.rs index bfc2439aa..3b39d63e7 100644 --- a/crates/graphql_schema_parser/src/peekable_lexer.rs +++ b/crates/graphql_schema_parser/src/peekable_lexer.rs @@ -46,7 +46,7 @@ impl<'source> PeekableLexer<'source> { self.end_index_of_last_parsed_token = self.current.span.end; let span = self.lexer_span(); // TODO why does self.current = ... not work here? - return std::mem::replace(&mut self.current, WithSpan::new(kind, span)); + std::mem::replace(&mut self.current, WithSpan::new(kind, span)) } pub fn peek(&self) -> WithSpan { diff --git a/crates/isograph_lang_parser/src/peekable_lexer.rs b/crates/isograph_lang_parser/src/peekable_lexer.rs index 313e4de48..22d42c394 100644 --- a/crates/isograph_lang_parser/src/peekable_lexer.rs +++ b/crates/isograph_lang_parser/src/peekable_lexer.rs @@ -47,7 +47,7 @@ impl<'source> PeekableLexer<'source> { self.end_index_of_last_parsed_token = self.current.span.end; let span = self.lexer_span(); // TODO why does self.current = ... not work here? - return std::mem::replace(&mut self.current, WithSpan::new(kind, span)); + std::mem::replace(&mut self.current, WithSpan::new(kind, span)) } pub fn peek(&self) -> WithSpan { diff --git a/crates/isograph_lang_types/src/client_field_declaration.rs b/crates/isograph_lang_types/src/client_field_declaration.rs index 8310acadf..6378dcd81 100644 --- a/crates/isograph_lang_types/src/client_field_declaration.rs +++ b/crates/isograph_lang_types/src/client_field_declaration.rs @@ -296,13 +296,13 @@ pub fn reachable_variables( vec![WithLocation::new(*name, non_constant_value.location)] } NonConstantValue::Object(object) => { - return object + object .iter() .flat_map(|pair| reachable_variables(&pair.value)) - .collect(); + .collect() } NonConstantValue::List(list) => { - return list.iter().flat_map(reachable_variables).collect(); + list.iter().flat_map(reachable_variables).collect() } _ => vec![], } diff --git a/crates/isograph_schema/src/isograph_schema.rs b/crates/isograph_schema/src/isograph_schema.rs index 3badde1bf..dcb0d7fde 100644 --- a/crates/isograph_schema/src/isograph_schema.rs +++ b/crates/isograph_schema/src/isograph_schema.rs @@ -287,7 +287,7 @@ impl ServerFieldData { pub type SchemaType<'a> = SelectionType<&'a SchemaObject, &'a SchemaScalar>; -pub fn get_name<'a>(schema_type: SchemaType<'a>) -> UnvalidatedTypeName { +pub fn get_name(schema_type: SchemaType<'_>) -> UnvalidatedTypeName { match schema_type { SelectionType::Object(object) => object.name.into(), SelectionType::Scalar(scalar) => scalar.name.item.into(), From ecfc176cd887ef5bfda84bb13698369ac0b68d3f Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 26 Dec 2024 00:27:07 +0900 Subject: [PATCH 41/61] cargo fmt --- .../src/client_field_declaration.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/isograph_lang_types/src/client_field_declaration.rs b/crates/isograph_lang_types/src/client_field_declaration.rs index 6378dcd81..f8b4ce34c 100644 --- a/crates/isograph_lang_types/src/client_field_declaration.rs +++ b/crates/isograph_lang_types/src/client_field_declaration.rs @@ -295,15 +295,11 @@ pub fn reachable_variables( NonConstantValue::Variable(name) => { vec![WithLocation::new(*name, non_constant_value.location)] } - NonConstantValue::Object(object) => { - object - .iter() - .flat_map(|pair| reachable_variables(&pair.value)) - .collect() - } - NonConstantValue::List(list) => { - list.iter().flat_map(reachable_variables).collect() - } + NonConstantValue::Object(object) => object + .iter() + .flat_map(|pair| reachable_variables(&pair.value)) + .collect(), + NonConstantValue::List(list) => list.iter().flat_map(reachable_variables).collect(), _ => vec![], } } From f62a09003631163ad7747de07ac389bb99e0d914 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 26 Dec 2024 00:29:14 +0900 Subject: [PATCH 42/61] Suppress warning in atomic_arena.rs - We are not actively maintaining or working on the relay crates, so we're okay with suppressing things that aren't trivial fixes. --- relay-crates/intern/src/atomic_arena.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/relay-crates/intern/src/atomic_arena.rs b/relay-crates/intern/src/atomic_arena.rs index f82bb336b..c2defac32 100644 --- a/relay-crates/intern/src/atomic_arena.rs +++ b/relay-crates/intern/src/atomic_arena.rs @@ -673,6 +673,7 @@ mod tests { use super::*; static mut ZERO: Zero<&str> = Zero::new("zero"); + #[allow(static_mut_refs)] static STRING_ARENA: AtomicArena<'static, &str> = AtomicArena::with_zero(unsafe { &ZERO }); /// For internal testing purposes we permit the unsafe synthesis of Refs. From d371028d80d8499c3758084db371f81479aa9224 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 25 Dec 2024 16:19:10 +0900 Subject: [PATCH 43/61] fix issues with cross and ci - do not use rust 1.75, since it is no longer supported (apparently!) - do not install rust in a step where it was unneeded --- .github/workflows/build-cli.yml | 5 +++-- .github/workflows/ci.yml | 11 ++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index c2af59794..61079c777 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -27,9 +27,10 @@ jobs: runs-on: ${{ inputs.os }} steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: 1.75.0 + toolchain: stable override: true target: ${{ inputs.target }} # more info here:- https://github.com/rust-lang/cargo/issues/13020 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b300e0fb3..b6bfef3f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,11 +61,6 @@ jobs: - folder: vite-demo steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.75.0 - override: true - target: x86_64-apple-darwin - name: 'Download cli binary' uses: actions/download-artifact@v4 with: @@ -181,9 +176,8 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - profile: minimal toolchain: stable components: rustfmt - name: Run cargo fmt @@ -199,9 +193,8 @@ jobs: steps: - uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - profile: minimal toolchain: stable components: rustfmt - name: Run cargo test From 583aeb8c99d0192c6bbd29cff7783542cf165f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Wed, 25 Dec 2024 16:39:03 +0100 Subject: [PATCH 44/61] Generate schema for `isograph.config.json` (#292) - generate a JSON schema for `isograph.config.json`, which users can then refer to via `"$schema": "path to schema"` for some added type safety --- .github/workflows/ci.yml | 19 ++++++++ .prettierignore | 1 + Cargo.lock | 44 ++++++++++++++++++ Cargo.toml | 1 + crates/isograph_config/Cargo.toml | 1 + .../src/compilation_options.rs | 23 +++++---- crates/isograph_config/src/main.rs | 7 +++ demos/github-demo/isograph.config.json | 1 + demos/pet-demo/isograph.config.json | 1 + demos/vite-demo/isograph.config.json | 1 + .../isograph-config-schema.json | Bin 0 -> 5048 bytes libs/isograph-react/isograph.config.json | 1 + package.json | 1 + 13 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 crates/isograph_config/src/main.rs create mode 100644 libs/isograph-compiler/isograph-config-schema.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6bfef3f8..f29f2c5c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,24 @@ jobs: run: ./artifacts/linux-x64/isograph_cli --config ./demos/${{ matrix.target.folder }}/isograph.config.json - name: 'Check working directory status' run: './scripts/check-git-status.sh' + build-json-schema: + name: Build json schema + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: 1.75.0 + override: true + target: x86_64-unknown-linux-musl + - name: Build isograph_config with cargo + run: cargo build --bin isograph_config --target x86_64-unknown-linux-musl --release + - name: Make artifact executable + run: chmod +x ./target/x86_64-unknown-linux-musl/release/isograph_config + - name: Build json schema + run: ./target/x86_64-unknown-linux-musl/release/isograph_config > ./libs/isograph-compiler/isograph-config-schema.json + - name: Check working directory status + run: './scripts/check-git-status.sh' typecheck-demos: name: Typecheck and Lint Demos @@ -208,6 +226,7 @@ jobs: build-js-packages, build-cli, build-cli-linux, + build-json-schema, build-demos, build-website, prettier, diff --git a/.prettierignore b/.prettierignore index e26a78ac1..334fc5efe 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ **/.docusaurus/** **/dist/** **/relay-crates/** +isograph-config-schema.json diff --git a/Cargo.lock b/Cargo.lock index a3fc1812c..707b7a377 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -395,6 +395,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + [[package]] name = "either" version = "1.13.0" @@ -637,6 +643,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -772,6 +779,7 @@ version = "0.3.0" dependencies = [ "colorize", "common_lang_types", + "schemars", "serde", "serde_json", "tracing", @@ -1304,6 +1312,31 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.72", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1339,6 +1372,17 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "serde_fmt" version = "1.0.3" diff --git a/Cargo.toml b/Cargo.toml index 103d190ef..c919de703 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ notify-debouncer-full = "0.4.0" pathdiff = "0.2.1" pretty-duration = "0.1.1" regex = "1.6.0" +schemars = { version="0.8.11", features=["indexmap1"] } serde = "1.0.197" serde_json = "1.0.108" strum = { version = "0.25.0", features = ["derive"] } diff --git a/crates/isograph_config/Cargo.toml b/crates/isograph_config/Cargo.toml index 6462a976a..d0cc9ae17 100644 --- a/crates/isograph_config/Cargo.toml +++ b/crates/isograph_config/Cargo.toml @@ -8,6 +8,7 @@ license = { workspace = true} [dependencies] common_lang_types = { path = "../common_lang_types" } +schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } colorize = { workspace = true } diff --git a/crates/isograph_config/src/compilation_options.rs b/crates/isograph_config/src/compilation_options.rs index 8582013ef..06e4c10ee 100644 --- a/crates/isograph_config/src/compilation_options.rs +++ b/crates/isograph_config/src/compilation_options.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; +use schemars::JsonSchema; use serde::Deserialize; use tracing::warn; @@ -80,9 +81,13 @@ impl Default for OptionalValidationLevel { } } -#[derive(Deserialize)] +#[derive(Deserialize, JsonSchema)] #[serde(deny_unknown_fields)] -struct ConfigFile { +pub struct IsographProjectConfig { + /// The user may hard-code the JSON Schema for their version of the config. + #[serde(rename = "$schema")] + #[allow(dead_code)] + pub json_schema: Option, /// The relative path to the folder where the compiler should look for Isograph literals pub project_root: PathBuf, /// The relative path to the folder where the compiler should create artifacts @@ -94,8 +99,8 @@ struct ConfigFile { #[serde(default)] pub schema_extensions: Vec, - /// Various that are of lesser importance - #[serde(default = "Default::default")] + /// Various options of less importance + #[serde(default)] pub options: ConfigFileOptions, } @@ -112,7 +117,7 @@ pub fn create_config(config_location: PathBuf) -> CompilerConfig { }, }; - let config_parsed: ConfigFile = serde_json::from_str(&config_contents) + let config_parsed: IsographProjectConfig = serde_json::from_str(&config_contents) .unwrap_or_else(|e| panic!("Error parsing config. Error: {}", e)); let mut config = config_location.clone(); @@ -179,17 +184,17 @@ pub fn create_config(config_location: PathBuf) -> CompilerConfig { } } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, JsonSchema)] #[serde(default, deny_unknown_fields)] -struct ConfigFileOptions { +pub struct ConfigFileOptions { on_invalid_id_type: ConfigFileOptionalValidationLevel, on_missing_babel_transform: ConfigFileOptionalValidationLevel, include_file_extensions_in_import_statements: bool, } -#[derive(Deserialize, Debug, Clone, Copy)] +#[derive(Deserialize, Debug, Clone, Copy, JsonSchema)] #[serde(rename_all = "snake_case")] -enum ConfigFileOptionalValidationLevel { +pub enum ConfigFileOptionalValidationLevel { /// If this validation error is encountered, it will be ignored Ignore, /// If this validation error is encountered, a warning will be issued diff --git a/crates/isograph_config/src/main.rs b/crates/isograph_config/src/main.rs new file mode 100644 index 000000000..66e841398 --- /dev/null +++ b/crates/isograph_config/src/main.rs @@ -0,0 +1,7 @@ +use isograph_config::IsographProjectConfig; +use schemars::schema_for; + +fn main() { + let schema = schema_for!(IsographProjectConfig); + println!("{}", serde_json::to_string_pretty(&schema).unwrap()); +} diff --git a/demos/github-demo/isograph.config.json b/demos/github-demo/isograph.config.json index d22a32976..f61a61cb3 100644 --- a/demos/github-demo/isograph.config.json +++ b/demos/github-demo/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../../libs/isograph-compiler/isograph-config-schema.json", "project_root": "./src/isograph-components", "schema": "./schema.graphql", "options": { diff --git a/demos/pet-demo/isograph.config.json b/demos/pet-demo/isograph.config.json index fdfdb8e3f..594a1680b 100644 --- a/demos/pet-demo/isograph.config.json +++ b/demos/pet-demo/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../../libs/isograph-compiler/isograph-config-schema.json", "project_root": "./src/components", "schema": "./backend/schema.graphql", "schema_extensions": ["./backend/schema-extension.graphql"], diff --git a/demos/vite-demo/isograph.config.json b/demos/vite-demo/isograph.config.json index ebf16a775..0eeafc821 100644 --- a/demos/vite-demo/isograph.config.json +++ b/demos/vite-demo/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../../libs/isograph-compiler/isograph-config-schema.json", "project_root": "./src/components", "schema": "./schema.graphql" } diff --git a/libs/isograph-compiler/isograph-config-schema.json b/libs/isograph-compiler/isograph-config-schema.json new file mode 100644 index 0000000000000000000000000000000000000000..db277797b212bdbd909d873498ccc8be23f981f9 GIT binary patch literal 5048 zcmds4O>fgc5ZyBp|G{#5q=g$2R}iSEqD4hqswzrr=K~zO#15rG{p-McGuiB}y-w0n zstQG|?e*+@y_q-b-@ksEr{;-C@Jr3Esm+C%nB2_F5YMkoIftcYB+b5}dM z8f$Oxp3yR64R*JE=&SrBl9|ViZRK@Fyx+D#@v~%+{l#u#rMrI9XI4Hrtd{pv!H1R8 zU}p(L$V!g&XjAPDv8zDGjg}P_?}2XjI}N*nw%wna9bsVN%Z?$pDlgPn_qp@!vVM+& zSi0RqOE*CWab;6a^;M9vQ#YPeDxN&Lp3NSzj|$*inXib}vR`{L5p!mkoK7TJVKJ0L zo=YL;Gn$>gn(1&qW!v-kA+q;QBI5M@XbzEq(2LMV`kf*opUdwa;{e(GAvrB!)zL(Ag<1$>WUx%>CiOR{((`u|ccrMES zie=YN`2$pd&gUs|z>`tePu3LC)^n7~Cc-M5{+#gzp3m`yt7Aw~ zgPvNuHA8&2sfl$v>D2-g%+|8453Ae1rh!lK=|TB$URVu&#o0ApyBONHo94v*BQbWhlP-YF0(_U_i;Gi&=jZtJA4uo%KfinQTs=U? z@pR48*9?DT(Eg~L<=DKAO)tB{Ub}fYhbNVI%`<)+(>5|r8)uTn<)#InGpvRcxsDC* z^Yp*Xgl*R$Hj~%6}s&J literal 0 HcmV?d00001 diff --git a/libs/isograph-react/isograph.config.json b/libs/isograph-react/isograph.config.json index 01e6a145d..168adf9b9 100644 --- a/libs/isograph-react/isograph.config.json +++ b/libs/isograph-react/isograph.config.json @@ -1,4 +1,5 @@ { + "$schema": "../isograph-compiler/isograph-config-schema.json", "project_root": "./src/tests", "schema": "./schema.graphql", "options": { diff --git a/package.json b/package.json index cfbfeca4c..3ed9688dd 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "watch-rs": "bacon -j build -p ./crates/", "check-rs": "bacon -j check -p ./crates/", "build": "cargo build", + "build-json-schema": "./target/debug/isograph_config > ./libs/isograph-compiler/isograph-config-schema.json", "watch-pet-demo": "pnpm --filter=pet-demo iso-watch", "watch-github-demo": "pnpm --filter=github-demo iso-watch", "watch-isograph-react-demo": "pnpm --filter=@isograph/react iso-watch", From 04324f793bf1891bdfb2d19eb38841787fc2f1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Thu, 26 Dec 2024 10:42:37 +0100 Subject: [PATCH 45/61] Fix `isograph-config-schema` being different in ci (#294) - write the JSON schema file directly from rust. Turns out using `stdout > file` results in different behavior in Windows and Linux, which this should fix --- .github/workflows/ci.yml | 7 ++++--- crates/isograph_config/src/main.rs | 9 ++++++++- .../isograph-config-schema.json | Bin 5048 -> 2429 bytes package.json | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f29f2c5c0..0f4c6edb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,9 +77,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: 1.75.0 + toolchain: stable override: true target: x86_64-unknown-linux-musl - name: Build isograph_config with cargo @@ -87,7 +88,7 @@ jobs: - name: Make artifact executable run: chmod +x ./target/x86_64-unknown-linux-musl/release/isograph_config - name: Build json schema - run: ./target/x86_64-unknown-linux-musl/release/isograph_config > ./libs/isograph-compiler/isograph-config-schema.json + run: ./target/x86_64-unknown-linux-musl/release/isograph_config - name: Check working directory status run: './scripts/check-git-status.sh' diff --git a/crates/isograph_config/src/main.rs b/crates/isograph_config/src/main.rs index 66e841398..b87ab1abf 100644 --- a/crates/isograph_config/src/main.rs +++ b/crates/isograph_config/src/main.rs @@ -1,7 +1,14 @@ +use std::fs; + use isograph_config::IsographProjectConfig; use schemars::schema_for; fn main() { let schema = schema_for!(IsographProjectConfig); - println!("{}", serde_json::to_string_pretty(&schema).unwrap()); + + fs::write( + "./libs/isograph-compiler/isograph-config-schema.json", + serde_json::to_string_pretty(&schema).unwrap(), + ) + .unwrap(); } diff --git a/libs/isograph-compiler/isograph-config-schema.json b/libs/isograph-compiler/isograph-config-schema.json index db277797b212bdbd909d873498ccc8be23f981f9..8ab82d23bad54e9d24b67c62a2f8c45d1a31182c 100644 GIT binary patch literal 2429 zcmcgtO;6k~5WVv&Mu8)+y;b7cs)z~|Dk?5j6?v1%#?-OXv9olA`0t&ulWg__S`}I@ z?9O^TZ|1#uemDTgj(kyLqp~x|>JZwq+3e0cdy=B3jux|$R25F%yq%@kVRkHeK?j49 zb>ZEDR9jz@yTc;9bGFip{>-Y4N8DU~Nio77ohB?f^()`QH?=7-C+E@`j`g7Uz_(b9 z&$LKEqvyPbl($Bn0-RQ&FG#mRJ1fw>)CiqNf<~>NR#cu8u0#kN{&;is32tKRP&tyA zCRieQ&IMP+Lq+W6Gy;!MLux1ij}ae8+tjXnXAHJ8CW{CeeEk0?E$}aj8>mzf@{$Ko z1V^i}nf=%ajS6~+(5g^Fa1m7HOvx?X*GQ?A!Zocn9P+j6ObG>{3J60r5AU&3oe6%p zVLEUavAkNFF3x{V)NXILinQxI^e1K92E7N}w2p$Z1+GV~jJc{tvB(?;w&P5X2<6x8 za8_catrqJ2EQ#;~Pvxoy(-Q*lxR;vgNd5^a#<}lNXv4aI(E&*rzizcxSJrE4Lp%LK z4C3FNsc3z#Ue0AXKVL$BL((GW|Grv|KLc7>i08=MTR0Vi$pvFw=1J5jl*ZkTG!9@C zky@=M!Xs*?wV!}JuqxcfWy@4qMky$BJ#~(X=Z(=&CTbb^&bDQv%&juIfgc5ZyBp|G{#5q=g$2R}iSEqD4hqswzrr=K~zO#15rG{p-McGuiB}y-w0n zstQG|?e*+@y_q-b-@ksEr{;-C@Jr3Esm+C%nB2_F5YMkoIftcYB+b5}dM z8f$Oxp3yR64R*JE=&SrBl9|ViZRK@Fyx+D#@v~%+{l#u#rMrI9XI4Hrtd{pv!H1R8 zU}p(L$V!g&XjAPDv8zDGjg}P_?}2XjI}N*nw%wna9bsVN%Z?$pDlgPn_qp@!vVM+& zSi0RqOE*CWab;6a^;M9vQ#YPeDxN&Lp3NSzj|$*inXib}vR`{L5p!mkoK7TJVKJ0L zo=YL;Gn$>gn(1&qW!v-kA+q;QBI5M@XbzEq(2LMV`kf*opUdwa;{e(GAvrB!)zL(Ag<1$>WUx%>CiOR{((`u|ccrMES zie=YN`2$pd&gUs|z>`tePu3LC)^n7~Cc-M5{+#gzp3m`yt7Aw~ zgPvNuHA8&2sfl$v>D2-g%+|8453Ae1rh!lK=|TB$URVu&#o0ApyBONHo94v*BQbWhlP-YF0(_U_i;Gi&=jZtJA4uo%KfinQTs=U? z@pR48*9?DT(Eg~L<=DKAO)tB{Ub}fYhbNVI%`<)+(>5|r8)uTn<)#InGpvRcxsDC* z^Yp*Xgl*R$Hj~%6}s&J diff --git a/package.json b/package.json index 3ed9688dd..2da35d8a8 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "watch-rs": "bacon -j build -p ./crates/", "check-rs": "bacon -j check -p ./crates/", "build": "cargo build", - "build-json-schema": "./target/debug/isograph_config > ./libs/isograph-compiler/isograph-config-schema.json", + "build-json-schema": "./target/debug/isograph_config", "watch-pet-demo": "pnpm --filter=pet-demo iso-watch", "watch-github-demo": "pnpm --filter=github-demo iso-watch", "watch-isograph-react-demo": "pnpm --filter=@isograph/react iso-watch", From 2f79ae11778529bbce0a829239e63f1a8c70c6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Sat, 28 Dec 2024 07:34:36 +0100 Subject: [PATCH 46/61] cache `rust build` with `turbo` (#296) --- .github/workflows/build-cli.yml | 17 ++++++++- .gitignore | 2 ++ package.json | 3 ++ pnpm-lock.yaml | 64 +++++++++++++++++++++++++++++++++ turbo.json | 13 +++++++ 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 turbo.json diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 61079c777..17641af42 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -27,6 +27,21 @@ jobs: runs-on: ${{ inputs.os }} steps: - uses: actions/checkout@v2 + - name: Cache turbo + uses: actions/cache@v4 + with: + path: .turbo + key: ${{ runner.os }}-turbo-${{ inputs.artifact-name }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-turbo-${{ inputs.artifact-name }}- + - uses: pnpm/action-setup@v2 + - name: Use Node.js 22 + uses: actions/setup-node@v3 + with: + node-version: 22 + cache: 'pnpm' + - name: Install dependencies + run: pnpm install --frozen-lockfile --ignore-scripts - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: @@ -44,7 +59,7 @@ jobs: command: install args: cross - name: 'Build isograph_cli with cargo (${{inputs.target}})' - run: ${{ inputs.cross && 'cross' || 'cargo' }} build --target ${{ inputs.target }} --release + run: pnpm exec turbo ${{ inputs.cross && 'cross' || 'build' }} -- --target ${{ inputs.target }} --release - uses: actions/upload-artifact@v4 with: name: ${{ inputs.artifact-name }} diff --git a/.gitignore b/.gitignore index e4a185676..7216a25a0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ artifacts **/out/** + +.turbo \ No newline at end of file diff --git a/package.json b/package.json index 2da35d8a8..d5ea48c2d 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,10 @@ { + "name": "isograph", "private": true, "devDependencies": { "gulp": "4.0.2", "prettier": "^3.2.4", + "turbo": "^2.2.3", "typescript": "5.6.3", "vitest": "^2.1.2" }, @@ -10,6 +12,7 @@ "watch-rs": "bacon -j build -p ./crates/", "check-rs": "bacon -j check -p ./crates/", "build": "cargo build", + "cross": "cross build", "build-json-schema": "./target/debug/isograph_config", "watch-pet-demo": "pnpm --filter=pet-demo iso-watch", "watch-github-demo": "pnpm --filter=github-demo iso-watch", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e79f9f65f..0cb26c848 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: prettier: specifier: ^3.2.4 version: 3.3.3 + turbo: + specifier: ^2.2.3 + version: 2.2.3 typescript: specifier: 5.6.3 version: 5.6.3 @@ -7192,6 +7195,40 @@ packages: peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + turbo-darwin-64@2.2.3: + resolution: {integrity: sha512-Rcm10CuMKQGcdIBS3R/9PMeuYnv6beYIHqfZFeKWVYEWH69sauj4INs83zKMTUiZJ3/hWGZ4jet9AOwhsssLyg==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@2.2.3: + resolution: {integrity: sha512-+EIMHkuLFqUdJYsA3roj66t9+9IciCajgj+DVek+QezEdOJKcRxlvDOS2BUaeN8kEzVSsNiAGnoysFWYw4K0HA==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@2.2.3: + resolution: {integrity: sha512-UBhJCYnqtaeOBQLmLo8BAisWbc9v9daL9G8upLR+XGj6vuN/Nz6qUAhverN4Pyej1g4Nt1BhROnj6GLOPYyqxQ==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@2.2.3: + resolution: {integrity: sha512-hJYT9dN06XCQ3jBka/EWvvAETnHRs3xuO/rb5bESmDfG+d9yQjeTMlhRXKrr4eyIMt6cLDt1LBfyi+6CQ+VAwQ==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@2.2.3: + resolution: {integrity: sha512-NPrjacrZypMBF31b4HE4ROg4P3nhMBPHKS5WTpMwf7wydZ8uvdEHpESVNMOtqhlp857zbnKYgP+yJF30H3N2dQ==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@2.2.3: + resolution: {integrity: sha512-fnNrYBCqn6zgKPKLHu4sOkihBI/+0oYFr075duRxqUZ+1aLWTAGfHZLgjVeLh3zR37CVzuerGIPWAEkNhkWEIw==} + cpu: [arm64] + os: [win32] + + turbo@2.2.3: + resolution: {integrity: sha512-5lDvSqIxCYJ/BAd6rQGK/AzFRhBkbu4JHVMLmGh/hCb7U3CqSnr5Tjwfy9vc+/5wG2DJ6wttgAaA7MoCgvBKZQ==} + hasBin: true + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -16450,6 +16487,33 @@ snapshots: tslib: 1.14.1 typescript: 5.6.3 + turbo-darwin-64@2.2.3: + optional: true + + turbo-darwin-arm64@2.2.3: + optional: true + + turbo-linux-64@2.2.3: + optional: true + + turbo-linux-arm64@2.2.3: + optional: true + + turbo-windows-64@2.2.3: + optional: true + + turbo-windows-arm64@2.2.3: + optional: true + + turbo@2.2.3: + optionalDependencies: + turbo-darwin-64: 2.2.3 + turbo-darwin-arm64: 2.2.3 + turbo-linux-64: 2.2.3 + turbo-linux-arm64: 2.2.3 + turbo-windows-64: 2.2.3 + turbo-windows-arm64: 2.2.3 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/turbo.json b/turbo.json new file mode 100644 index 000000000..022042937 --- /dev/null +++ b/turbo.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "//#build": { + "inputs": ["crates/**", "relay-crates/**", "Cargo.*"], + "outputs": ["**/release/isograph_cli*", "**/debug/isograph_cli*"] + }, + "//#cross": { + "inputs": ["crates/**", "relay-crates/**", "Cargo.*"], + "outputs": ["**/release/isograph_cli*", "**/debug/isograph_cli*"] + } + } +} From d10bce3e4e2ed10b995a891dc03a86a011b8e1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Sat, 28 Dec 2024 07:58:47 +0100 Subject: [PATCH 47/61] add magic `link` field (#282) - Add a magic `link` field that can be selected on any item, whose type is `Link` - This is analogous to Relay's `__id` field - This field is used internally by e.g. `asUser`, whose resolver is implemented as `data.__typename === 'User' ? data.link : null` - Within the runtime store APIs, a `Link` is the data structure that is used to navigate between two items. e.g. the store might contain `{ Query: { pets: [{ __link: '0', __typename: 'Pet' }] }`. `{ __link: '0', __typename: 'Pet' }` is a `Link` --- .../src/eager_reader_artifact.rs | 15 +- .../src/generate_artifacts.rs | 143 +++++++++++------- .../src/import_statements.rs | 1 + .../src/iso_overload_file.rs | 1 + .../src/reader_ast.rs | 51 +++++-- crates/isograph_compiler/src/source_files.rs | 1 + crates/isograph_schema/src/add_link_fields.rs | 52 +++++++ .../src/add_pointers_to_supertypes.rs | 30 +++- .../src/create_merged_selection_set.rs | 29 ++-- crates/isograph_schema/src/lib.rs | 1 + .../src/process_client_field_declaration.rs | 1 + crates/isograph_schema/src/validate_schema.rs | 1 + .../__isograph/User/asUser/resolver_reader.ts | 10 +- .../AdItem/asAdItem/resolver_reader.ts | 10 +- .../BlogItem/asBlogItem/resolver_reader.ts | 10 +- .../src/core/areEqualWithDeepComparison.ts | 19 +++ libs/isograph-react/src/core/read.ts | 5 + libs/isograph-react/src/core/reader.ts | 9 +- 18 files changed, 291 insertions(+), 98 deletions(-) create mode 100644 crates/isograph_schema/src/add_link_fields.rs diff --git a/crates/graphql_artifact_generation/src/eager_reader_artifact.rs b/crates/graphql_artifact_generation/src/eager_reader_artifact.rs index 57dcd8982..d7dee8d07 100644 --- a/crates/graphql_artifact_generation/src/eager_reader_artifact.rs +++ b/crates/graphql_artifact_generation/src/eager_reader_artifact.rs @@ -156,10 +156,10 @@ pub(crate) fn generate_eager_reader_condition_artifact( reader_imports_to_import_statement(&reader_imports, file_extensions); let reader_param_type = "{ data: any, parameters: Record }"; - let reader_output_type = "boolean"; + let reader_output_type = "Link | null"; let reader_content = format!( - "import type {{ EagerReaderArtifact, ReaderAst }} from '@isograph/react';\n\ + "import type {{ EagerReaderArtifact, ReaderAst, Link }} from '@isograph/react';\n\ {reader_import_statement}\n\ const readerAst: ReaderAst<{reader_param_type}> = {reader_ast};\n\n\ const artifact: EagerReaderArtifact<\n\ @@ -167,7 +167,7 @@ pub(crate) fn generate_eager_reader_condition_artifact( {}{reader_output_type}\n\ > = {{\n\ {}kind: \"EagerReaderArtifact\",\n\ - {}resolver: ({{ data }}) => data.__typename === \"{concrete_type}\",\n\ + {}resolver: ({{ data }}) => data.__typename === \"{concrete_type}\" ? data.link : null,\n\ {}readerAst,\n\ }};\n\n\ export default artifact;\n", @@ -196,6 +196,7 @@ pub(crate) fn generate_eager_reader_param_type_artifact( let mut param_type_imports = BTreeSet::new(); let mut loadable_fields = BTreeSet::new(); + let mut link_fields = false; let client_field_parameter_type = generate_client_field_parameter_type( schema, client_field.selection_set_for_parent_query(), @@ -203,12 +204,19 @@ pub(crate) fn generate_eager_reader_param_type_artifact( &mut param_type_imports, &mut loadable_fields, 1, + &mut link_fields, ); let param_type_import_statement = param_type_imports_to_import_statement(¶m_type_imports, file_extensions); let reader_param_type = format!("{}__{}__param", parent_type.name, client_field.name); + let link_field_imports = if link_fields { + "import type { Link } from '@isograph/react';\n".to_string() + } else { + "".to_string() + }; + let loadable_field_imports = if !loadable_fields.is_empty() { let param_imports = param_type_imports_to_import_param_statement(&loadable_fields, file_extensions); @@ -234,6 +242,7 @@ pub(crate) fn generate_eager_reader_param_type_artifact( let indent = " "; let param_type_content = format!( "{param_type_import_statement}\ + {link_field_imports}\ {loadable_field_imports}\ {parameters_import}\n\ export type {reader_param_type} = {{\n\ diff --git a/crates/graphql_artifact_generation/src/generate_artifacts.rs b/crates/graphql_artifact_generation/src/generate_artifacts.rs index 9269d83a4..4db9e470f 100644 --- a/crates/graphql_artifact_generation/src/generate_artifacts.rs +++ b/crates/graphql_artifact_generation/src/generate_artifacts.rs @@ -36,7 +36,7 @@ use crate::{ generate_entrypoint_artifacts_with_client_field_traversal_result, }, format_parameter_type::format_parameter_type, - import_statements::ParamTypeImports, + import_statements::{LinkImports, ParamTypeImports}, iso_overload_file::build_iso_overload_artifact, refetch_reader_artifact::{ generate_refetch_output_type_artifact, generate_refetch_reader_artifact, @@ -131,8 +131,9 @@ pub fn get_artifact_path_and_content( } FieldType::ClientField(encountered_client_field_id) => { let encountered_client_field = schema.client_field(*encountered_client_field_id); - // Generate reader ASTs for all encountered client fields, which may be reader or refetch reader + match &encountered_client_field.variant { + ClientFieldVariant::Link => (), ClientFieldVariant::UserWritten(info) => { path_and_contents.extend(generate_eager_reader_artifacts( schema, @@ -248,6 +249,7 @@ pub fn get_artifact_path_and_content( for user_written_client_field in schema.client_fields.iter().flat_map(|field| match field { ClientType::ClientField(field) => match field.variant { + ClientFieldVariant::Link => None, ClientFieldVariant::UserWritten(_) => Some(field), ClientFieldVariant::ImperativelyLoadedField(_) => None, }, @@ -283,20 +285,25 @@ pub fn get_artifact_path_and_content( for output_type_id in encountered_output_types { let client_field = schema.client_field(output_type_id); - let path_and_content = match client_field.variant { - ClientFieldVariant::UserWritten(info) => generate_eager_reader_output_type_artifact( - schema, - client_field, - project_root, - artifact_directory, - info, - file_extensions, - ), + let artifact_path_and_content = match client_field.variant { + ClientFieldVariant::Link => None, + ClientFieldVariant::UserWritten(info) => { + Some(generate_eager_reader_output_type_artifact( + schema, + client_field, + project_root, + artifact_directory, + info, + file_extensions, + )) + } ClientFieldVariant::ImperativelyLoadedField(_) => { - generate_refetch_output_type_artifact(schema, client_field) + Some(generate_refetch_output_type_artifact(schema, client_field)) } }; - path_and_contents.push(path_and_content); + if let Some(path_and_content) = artifact_path_and_content { + path_and_contents.push(path_and_content); + }; } path_and_contents.push(build_iso_overload_artifact( @@ -403,6 +410,7 @@ pub(crate) fn get_serialized_field_arguments( pub(crate) fn generate_output_type(client_field: &ValidatedClientField) -> ClientFieldOutputType { let variant = &client_field.variant; match variant { + ClientFieldVariant::Link => ClientFieldOutputType("Link".to_string()), ClientFieldVariant::UserWritten(info) => match info.user_written_component_variant { UserWrittenComponentVariant::Eager => { ClientFieldOutputType("ReturnType".to_string()) @@ -438,6 +446,7 @@ pub(crate) fn generate_client_field_parameter_type( nested_client_field_imports: &mut ParamTypeImports, loadable_fields: &mut ParamTypeImports, indentation_level: u8, + link_fields: &mut LinkImports, ) -> ClientFieldParameterType { // TODO use unwraps let mut client_field_parameter_type = "{\n".to_string(); @@ -450,6 +459,7 @@ pub(crate) fn generate_client_field_parameter_type( nested_client_field_imports, loadable_fields, indentation_level + 1, + link_fields, ); } client_field_parameter_type.push_str(&format!("{}}}", " ".repeat(indentation_level as usize))); @@ -457,6 +467,7 @@ pub(crate) fn generate_client_field_parameter_type( ClientFieldParameterType(client_field_parameter_type) } +#[allow(clippy::too_many_arguments)] fn write_param_type_from_selection( schema: &ValidatedSchema, query_type_declaration: &mut String, @@ -465,6 +476,7 @@ fn write_param_type_from_selection( nested_client_field_imports: &mut ParamTypeImports, loadable_fields: &mut ParamTypeImports, indentation_level: u8, + link_fields: &mut LinkImports, ) { match &selection.item { ServerFieldSelection::ScalarField(scalar_field_selection) => { @@ -516,55 +528,71 @@ fn write_param_type_from_selection( query_type_declaration .push_str(&" ".repeat(indentation_level as usize).to_string()); - nested_client_field_imports.insert(client_field.type_and_field); - let inner_output_type = format!( - "{}__output_type", - client_field.type_and_field.underscore_separated() - ); - - let output_type = match scalar_field_selection.associated_data.selection_variant - { - ValidatedIsographSelectionVariant::Regular => inner_output_type, - ValidatedIsographSelectionVariant::Loadable(_) => { - loadable_fields.insert(client_field.type_and_field); - let provided_arguments = get_provided_arguments( - client_field.variable_definitions.iter().map(|x| &x.item), - &scalar_field_selection.arguments, + match client_field.variant { + ClientFieldVariant::Link => { + *link_fields = true; + let output_type = "Link"; + query_type_declaration.push_str( + &(format!( + "readonly {}: {},\n", + scalar_field_selection.name_or_alias().item, + output_type + )), ); + } + ClientFieldVariant::UserWritten(_) + | ClientFieldVariant::ImperativelyLoadedField(_) => { + nested_client_field_imports.insert(client_field.type_and_field); + let inner_output_type = format!( + "{}__output_type", + client_field.type_and_field.underscore_separated() + ); + let output_type = match scalar_field_selection + .associated_data + .selection_variant + { + ValidatedIsographSelectionVariant::Regular => inner_output_type, + ValidatedIsographSelectionVariant::Loadable(_) => { + loadable_fields.insert(client_field.type_and_field); + let provided_arguments = get_provided_arguments( + client_field.variable_definitions.iter().map(|x| &x.item), + &scalar_field_selection.arguments, + ); - let indent = " ".repeat((indentation_level + 1) as usize); - let provided_args_type = if provided_arguments.is_empty() { - "".to_string() - } else { - format!( - ",\n{indent}Omit, keyof {}>", - client_field.type_and_field.underscore_separated(), - get_loadable_field_type_from_arguments( - schema, - provided_arguments + let indent = " ".repeat((indentation_level + 1) as usize); + let provided_args_type = if provided_arguments.is_empty() { + "".to_string() + } else { + format!( + ",\n{indent}Omit, keyof {}>", + client_field.type_and_field.underscore_separated(), + get_loadable_field_type_from_arguments( + schema, + provided_arguments + ) + ) + }; + + format!( + "LoadableField<\n\ + {indent}{}__param,\n\ + {indent}{inner_output_type}\ + {provided_args_type}\n\ + {}>", + client_field.type_and_field.underscore_separated(), + " ".repeat(indentation_level as usize), ) - ) + } }; - - format!( - "LoadableField<\n\ - {indent}{}__param,\n\ - {indent}{inner_output_type}\ - {provided_args_type}\n\ - {}>", - client_field.type_and_field.underscore_separated(), - " ".repeat(indentation_level as usize), - ) + query_type_declaration.push_str( + &(format!( + "readonly {}: {},\n", + scalar_field_selection.name_or_alias().item, + output_type + )), + ); } - }; - - query_type_declaration.push_str( - &(format!( - "readonly {}: {},\n", - scalar_field_selection.name_or_alias().item, - output_type - )), - ); + } } } } @@ -603,6 +631,7 @@ fn write_param_type_from_selection( nested_client_field_imports, loadable_fields, indentation_level, + link_fields, ) }), }; diff --git a/crates/graphql_artifact_generation/src/import_statements.rs b/crates/graphql_artifact_generation/src/import_statements.rs index 176feaf8a..36c75958b 100644 --- a/crates/graphql_artifact_generation/src/import_statements.rs +++ b/crates/graphql_artifact_generation/src/import_statements.rs @@ -22,6 +22,7 @@ impl ImportedFileCategory { pub(crate) type ReaderImports = BTreeSet<(ObjectTypeAndFieldName, ImportedFileCategory)>; pub(crate) type ParamTypeImports = BTreeSet; +pub(crate) type LinkImports = bool; pub(crate) fn reader_imports_to_import_statement( reader_imports: &ReaderImports, diff --git a/crates/graphql_artifact_generation/src/iso_overload_file.rs b/crates/graphql_artifact_generation/src/iso_overload_file.rs index 85d347c0c..3a04edb99 100644 --- a/crates/graphql_artifact_generation/src/iso_overload_file.rs +++ b/crates/graphql_artifact_generation/src/iso_overload_file.rs @@ -263,6 +263,7 @@ fn user_written_fields( .iter() .filter_map(|client_field| match client_field { ClientType::ClientField(client_field) => match client_field.variant { + ClientFieldVariant::Link => None, ClientFieldVariant::UserWritten(info) => { Some((client_field, info.user_written_component_variant)) } diff --git a/crates/graphql_artifact_generation/src/reader_ast.rs b/crates/graphql_artifact_generation/src/reader_ast.rs index 789f1c281..076413a9b 100644 --- a/crates/graphql_artifact_generation/src/reader_ast.rs +++ b/crates/graphql_artifact_generation/src/reader_ast.rs @@ -5,9 +5,9 @@ use isograph_lang_types::{ LoadableDirectiveParameters, RefetchQueryIndex, SelectionType, ServerFieldSelection, }; use isograph_schema::{ - categorize_field_loadability, transform_arguments_with_child_context, FieldType, Loadability, - NameAndArguments, NormalizationKey, ObjectTypeAndFieldName, PathToRefetchField, - RefetchedPathsMap, SchemaServerFieldVariant, ValidatedClientField, + categorize_field_loadability, transform_arguments_with_child_context, ClientFieldVariant, + FieldType, Loadability, NameAndArguments, NormalizationKey, ObjectTypeAndFieldName, + PathToRefetchField, RefetchedPathsMap, SchemaServerFieldVariant, ValidatedClientField, ValidatedIsographSelectionVariant, ValidatedLinkedFieldSelection, ValidatedScalarFieldSelection, ValidatedSchema, ValidatedSelection, VariableContext, }; @@ -204,20 +204,43 @@ fn scalar_client_defined_field_ast_node( indentation_level, scalar_field_selection, ), - None => user_written_variant_ast_node( - scalar_field_selection, - indentation_level, - client_field, - schema, - path, - root_refetched_paths, - reader_imports, - &client_field_variable_context, - parent_variable_context, - ), + None => match client_field.variant { + ClientFieldVariant::Link => { + link_variant_ast_node(scalar_field_selection, indentation_level) + } + ClientFieldVariant::UserWritten(_) | ClientFieldVariant::ImperativelyLoadedField(_) => { + user_written_variant_ast_node( + scalar_field_selection, + indentation_level, + client_field, + schema, + path, + root_refetched_paths, + reader_imports, + &client_field_variable_context, + parent_variable_context, + ) + } + }, } } +fn link_variant_ast_node( + scalar_field_selection: &ValidatedScalarFieldSelection, + indentation_level: u8, +) -> String { + let alias = scalar_field_selection.name_or_alias().item; + let indent_1 = " ".repeat(indentation_level as usize); + let indent_2 = " ".repeat((indentation_level + 1) as usize); + + format!( + "{indent_1}{{\n\ + {indent_2}kind: \"Link\",\n\ + {indent_2}alias: \"{alias}\",\n\ + {indent_1}}},\n", + ) +} + #[allow(clippy::too_many_arguments)] fn user_written_variant_ast_node( scalar_field_selection: &ValidatedScalarFieldSelection, diff --git a/crates/isograph_compiler/src/source_files.rs b/crates/isograph_compiler/src/source_files.rs index aad3c0235..f96f9b95c 100644 --- a/crates/isograph_compiler/src/source_files.rs +++ b/crates/isograph_compiler/src/source_files.rs @@ -67,6 +67,7 @@ impl SourceFiles { process_iso_literals(schema, self.contains_iso)?; process_exposed_fields(schema)?; schema.add_fields_to_subtypes(&outcome.type_refinement_maps.supertype_to_subtype_map)?; + schema.add_link_fields()?; schema .add_pointers_to_supertypes(&outcome.type_refinement_maps.subtype_to_supertype_map)?; add_refetch_fields_to_objects(schema)?; diff --git a/crates/isograph_schema/src/add_link_fields.rs b/crates/isograph_schema/src/add_link_fields.rs new file mode 100644 index 000000000..74ba9c445 --- /dev/null +++ b/crates/isograph_schema/src/add_link_fields.rs @@ -0,0 +1,52 @@ +use crate::{ + ClientField, ClientFieldVariant, ClientType, FieldType, ObjectTypeAndFieldName, + ProcessTypeDefinitionError, ProcessTypeDefinitionResult, UnvalidatedSchema, LINK_FIELD_NAME, +}; +use common_lang_types::{Location, WithLocation}; +use intern::string_key::Intern; + +impl UnvalidatedSchema { + pub fn add_link_fields(&mut self) -> ProcessTypeDefinitionResult<()> { + for object in &mut self.server_field_data.server_objects { + let field_name = (*LINK_FIELD_NAME).into(); + let next_client_field_id = self.client_fields.len().into(); + self.client_fields + .push(ClientType::ClientField(ClientField { + description: Some( + format!("A store Link for the {} type.", object.name) + .intern() + .into(), + ), + id: next_client_field_id, + name: field_name, + parent_object_id: object.id, + variable_definitions: vec![], + reader_selection_set: Some(vec![]), + variant: ClientFieldVariant::Link, + type_and_field: ObjectTypeAndFieldName { + field_name, + type_name: object.name, + }, + refetch_strategy: None, + })); + + if object + .encountered_fields + .insert( + field_name, + FieldType::ClientField(ClientType::ClientField(next_client_field_id)), + ) + .is_some() + { + return Err(WithLocation::new( + ProcessTypeDefinitionError::FieldExistsOnType { + field_name, + parent_type: object.name, + }, + Location::generated(), + )); + } + } + Ok(()) + } +} diff --git a/crates/isograph_schema/src/add_pointers_to_supertypes.rs b/crates/isograph_schema/src/add_pointers_to_supertypes.rs index abe3cdc81..f5a12f9a6 100644 --- a/crates/isograph_schema/src/add_pointers_to_supertypes.rs +++ b/crates/isograph_schema/src/add_pointers_to_supertypes.rs @@ -4,11 +4,11 @@ use intern::string_key::Intern; use isograph_lang_types::{ScalarFieldSelection, ServerFieldSelection}; use crate::{ - FieldType, ProcessTypeDefinitionError, ProcessTypeDefinitionResult, SchemaObject, + ClientType, FieldType, ProcessTypeDefinitionError, ProcessTypeDefinitionResult, SchemaObject, SchemaServerField, SchemaServerFieldVariant, ServerFieldTypeAssociatedData, ServerFieldTypeAssociatedDataInlineFragment, UnvalidatedSchema, ValidatedIsographSelectionVariant, ValidatedScalarFieldAssociatedData, - ValidatedTypeRefinementMap, + ValidatedTypeRefinementMap, LINK_FIELD_NAME, }; use common_lang_types::Location; impl UnvalidatedSchema { @@ -56,7 +56,31 @@ impl UnvalidatedSchema { Span::todo_generated(), ); - let condition_selection_set = vec![typename_selection]; + let link_selection = WithSpan::new( + ServerFieldSelection::ScalarField(ScalarFieldSelection { + arguments: vec![], + associated_data: ValidatedScalarFieldAssociatedData { + location: FieldType::ClientField( + match *subtype + .encountered_fields + .get(&(*LINK_FIELD_NAME).into()) + .expect("Expected link to exist") + .as_client_field() + .expect("Expected link to be client field") + { + ClientType::ClientField(client_field_id) => client_field_id, + }, + ), + selection_variant: ValidatedIsographSelectionVariant::Regular, + }, + directives: vec![], + name: WithLocation::new(*LINK_FIELD_NAME, Location::generated()), + reader_alias: None, + }), + Span::todo_generated(), + ); + + let condition_selection_set = vec![typename_selection, link_selection]; let server_field = SchemaServerField { description: Some( diff --git a/crates/isograph_schema/src/create_merged_selection_set.rs b/crates/isograph_schema/src/create_merged_selection_set.rs index 44bc3b198..615756e20 100644 --- a/crates/isograph_schema/src/create_merged_selection_set.rs +++ b/crates/isograph_schema/src/create_merged_selection_set.rs @@ -18,7 +18,7 @@ use lazy_static::lazy_static; use crate::{ categorize_field_loadability, create_transformed_name_and_arguments, expose_field_directive::RequiresRefinement, transform_arguments_with_child_context, - transform_name_and_arguments_with_child_variable_context, FieldType, + transform_name_and_arguments_with_child_variable_context, ClientFieldVariant, FieldType, ImperativelyLoadedFieldVariant, Loadability, NameAndArguments, PathToRefetchField, RootOperationName, SchemaObject, SchemaServerFieldVariant, UnvalidatedVariableDefinition, ValidatedClientField, ValidatedIsographSelectionVariant, ValidatedScalarFieldSelection, @@ -44,6 +44,7 @@ lazy_static! { pub static ref REFETCH_FIELD_NAME: ScalarFieldName = "__refetch".intern().into(); pub static ref NODE_FIELD_NAME: LinkedFieldName = "node".intern().into(); pub static ref TYPENAME_FIELD_NAME: ScalarFieldName = "__typename".intern().into(); + pub static ref LINK_FIELD_NAME: ScalarFieldName = "link".intern().into(); } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -713,16 +714,22 @@ fn merge_validated_selections_into_selection_map( variant, ); } - None => merge_non_loadable_scalar_client_field( - parent_type, - schema, - parent_map, - merge_traversal_state, - newly_encountered_scalar_client_field, - encountered_client_field_map, - variable_context, - &scalar_field_selection.arguments, - ), + None => match newly_encountered_scalar_client_field.variant { + ClientFieldVariant::Link => {} + ClientFieldVariant::ImperativelyLoadedField(_) + | ClientFieldVariant::UserWritten(_) => { + merge_non_loadable_scalar_client_field( + parent_type, + schema, + parent_map, + merge_traversal_state, + newly_encountered_scalar_client_field, + encountered_client_field_map, + variable_context, + &scalar_field_selection.arguments, + ) + } + }, } merge_traversal_state diff --git a/crates/isograph_schema/src/lib.rs b/crates/isograph_schema/src/lib.rs index 9367d1685..909a25cba 100644 --- a/crates/isograph_schema/src/lib.rs +++ b/crates/isograph_schema/src/lib.rs @@ -1,5 +1,6 @@ mod accessible_client_fields_iterator; mod add_fields_to_subtypes; +mod add_link_fields; mod add_pointers_to_supertypes; mod argument_map; mod create_merged_selection_set; diff --git a/crates/isograph_schema/src/process_client_field_declaration.rs b/crates/isograph_schema/src/process_client_field_declaration.rs index 5eb80815b..f75ed1214 100644 --- a/crates/isograph_schema/src/process_client_field_declaration.rs +++ b/crates/isograph_schema/src/process_client_field_declaration.rs @@ -199,6 +199,7 @@ pub struct UserWrittenClientFieldInfo { pub enum ClientFieldVariant { UserWritten(UserWrittenClientFieldInfo), ImperativelyLoadedField(ImperativelyLoadedFieldVariant), + Link, } lazy_static! { diff --git a/crates/isograph_schema/src/validate_schema.rs b/crates/isograph_schema/src/validate_schema.rs index 3cad920c2..061ce057e 100644 --- a/crates/isograph_schema/src/validate_schema.rs +++ b/crates/isograph_schema/src/validate_schema.rs @@ -331,6 +331,7 @@ pub fn categorize_field_loadability<'a>( selection_variant: &'a ValidatedIsographSelectionVariant, ) -> Option> { match &client_field.variant { + ClientFieldVariant::Link => None, ClientFieldVariant::UserWritten(_) => match selection_variant { ValidatedIsographSelectionVariant::Regular => None, ValidatedIsographSelectionVariant::Loadable((l, _)) => { diff --git a/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts b/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts index aa9b966b7..6c7cc03ef 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/asUser/resolver_reader.ts @@ -1,4 +1,4 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type { EagerReaderArtifact, ReaderAst, Link } from '@isograph/react'; const readerAst: ReaderAst<{ data: any, parameters: Record }> = [ { @@ -7,14 +7,18 @@ const readerAst: ReaderAst<{ data: any, parameters: Record } alias: null, arguments: null, }, + { + kind: "Link", + alias: "link", + }, ]; const artifact: EagerReaderArtifact< { data: any, parameters: Record }, - boolean + Link | null > = { kind: "EagerReaderArtifact", - resolver: ({ data }) => data.__typename === "User", + resolver: ({ data }) => data.__typename === "User" ? data.link : null, readerAst, }; diff --git a/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts index 2000168ff..7562bd540 100644 --- a/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/AdItem/asAdItem/resolver_reader.ts @@ -1,4 +1,4 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type { EagerReaderArtifact, ReaderAst, Link } from '@isograph/react'; const readerAst: ReaderAst<{ data: any, parameters: Record }> = [ { @@ -7,14 +7,18 @@ const readerAst: ReaderAst<{ data: any, parameters: Record } alias: null, arguments: null, }, + { + kind: "Link", + alias: "link", + }, ]; const artifact: EagerReaderArtifact< { data: any, parameters: Record }, - boolean + Link | null > = { kind: "EagerReaderArtifact", - resolver: ({ data }) => data.__typename === "AdItem", + resolver: ({ data }) => data.__typename === "AdItem" ? data.link : null, readerAst, }; diff --git a/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts b/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts index f65548744..5bb1d6963 100644 --- a/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts +++ b/demos/pet-demo/src/components/__isograph/BlogItem/asBlogItem/resolver_reader.ts @@ -1,4 +1,4 @@ -import type { EagerReaderArtifact, ReaderAst } from '@isograph/react'; +import type { EagerReaderArtifact, ReaderAst, Link } from '@isograph/react'; const readerAst: ReaderAst<{ data: any, parameters: Record }> = [ { @@ -7,14 +7,18 @@ const readerAst: ReaderAst<{ data: any, parameters: Record } alias: null, arguments: null, }, + { + kind: "Link", + alias: "link", + }, ]; const artifact: EagerReaderArtifact< { data: any, parameters: Record }, - boolean + Link | null > = { kind: "EagerReaderArtifact", - resolver: ({ data }) => data.__typename === "BlogItem", + resolver: ({ data }) => data.__typename === "BlogItem" ? data.link : null, readerAst, }; diff --git a/libs/isograph-react/src/core/areEqualWithDeepComparison.ts b/libs/isograph-react/src/core/areEqualWithDeepComparison.ts index 2a4bb5570..8574cbf0e 100644 --- a/libs/isograph-react/src/core/areEqualWithDeepComparison.ts +++ b/libs/isograph-react/src/core/areEqualWithDeepComparison.ts @@ -1,3 +1,4 @@ +import type { Link } from './IsographEnvironment'; import type { ReaderAst, ReaderLinkedField, ReaderScalarField } from './reader'; export function mergeUsingReaderAst( field: ReaderScalarField | ReaderLinkedField, @@ -96,6 +97,24 @@ export function mergeObjectsUsingReaderAst( } break; } + case 'Link': { + const key = field.alias; + // @ts-expect-error + const oldValue: Link = oldItemObject[key]; + // @ts-expect-error + const newValue: Link = newItemObject[key]; + + if ( + oldValue.__link !== newValue.__link || + oldValue.__typename !== newValue.__typename + ) { + canRecycle = false; + } else { + // @ts-expect-error + newItemObject[key] = oldValue; + } + break; + } case 'ImperativelyLoadedField': case 'LoadablySelectedField': break; diff --git a/libs/isograph-react/src/core/read.ts b/libs/isograph-react/src/core/read.ts index 2ccb9ecf2..78d17ce75 100644 --- a/libs/isograph-react/src/core/read.ts +++ b/libs/isograph-react/src/core/read.ts @@ -164,6 +164,10 @@ function readData( target[field.alias ?? field.fieldName] = value; break; } + case 'Link': { + target[field.alias] = root; + break; + } case 'Linked': { const storeRecordName = getParentRecordKey(field, variables); const value = storeRecord[storeRecordName]; @@ -610,6 +614,7 @@ function readData( } break; } + default: { // Ensure we have covered all variants let _: never = field; diff --git a/libs/isograph-react/src/core/reader.ts b/libs/isograph-react/src/core/reader.ts index 41ff0abd5..8d087ba77 100644 --- a/libs/isograph-react/src/core/reader.ts +++ b/libs/isograph-react/src/core/reader.ts @@ -79,7 +79,8 @@ export type ReaderAstNode = | ReaderLinkedField | ReaderNonLoadableResolverField | ReaderImperativelyLoadedField - | ReaderLoadableField; + | ReaderLoadableField + | ReaderLinkeField; // @ts-ignore export type ReaderAst = ReadonlyArray; @@ -90,6 +91,12 @@ export type ReaderScalarField = { readonly alias: string | null; readonly arguments: Arguments | null; }; + +export type ReaderLinkeField = { + readonly kind: 'Link'; + readonly alias: string; +}; + export type ReaderLinkedField = { readonly kind: 'Linked'; readonly fieldName: string; From d0a28ccf143704020e6294f99075f5cf1b961e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Sat, 28 Dec 2024 08:56:37 +0100 Subject: [PATCH 48/61] Add kind to normalization ast (#297) This converts NormalizationAst from NormalizationAstNode[] to object with kind: "NormalizationAst" so we can make it a union of NormalizationAstLoader and NormalizationAst in #271 --- .../src/entrypoint_artifact.rs | 9 +- .../src/imperatively_loaded_fields.rs | 9 +- .../__isograph/Query/HomePage/__refetch__0.ts | 351 ++++++------- .../__isograph/Query/HomePage/entrypoint.ts | 321 ++++++------ .../Query/PullRequest/entrypoint.ts | 271 +++++----- .../Query/RepositoryPage/entrypoint.ts | 491 +++++++++--------- .../__isograph/Query/UserPage/entrypoint.ts | 355 ++++++------- .../User/RepositoryConnection/entrypoint.ts | 321 ++++++------ .../AdItem/AdItemDisplay/entrypoint.ts | 83 +-- .../BlogItem/BlogItemMoreDetail/entrypoint.ts | 73 +-- .../Image/ImageDisplay/entrypoint.ts | 73 +-- .../Mutation/SetTagline/entrypoint.ts | 67 +-- .../Pet/PetCheckinsCard/__refetch__0.ts | 99 ++-- .../Pet/PetCheckinsCard/entrypoint.ts | 125 ++--- .../Pet/PetCheckinsCardList/__refetch__0.ts | 99 ++-- .../Pet/PetCheckinsCardList/entrypoint.ts | 125 ++--- .../__isograph/Query/HomeRoute/entrypoint.ts | 63 +-- .../__isograph/Query/Newsfeed/entrypoint.ts | 195 +++---- .../__isograph/Query/PetByName/entrypoint.ts | 51 +- .../Query/PetCheckinListRoute/__refetch__0.ts | 89 ++-- .../Query/PetCheckinListRoute/entrypoint.ts | 103 ++-- .../PetDetailDeferredRoute/entrypoint.ts | 51 +- .../Query/PetDetailRoute/__refetch__0.ts | 357 ++++++------- .../Query/PetDetailRoute/__refetch__1.ts | 359 ++++++------- .../Query/PetDetailRoute/__refetch__2.ts | 351 ++++++------- .../Query/PetDetailRoute/__refetch__3.ts | 99 ++-- .../Query/PetDetailRoute/__refetch__4.ts | 107 ++-- .../Query/PetDetailRoute/entrypoint.ts | 335 ++++++------ .../Query/PetFavoritePhrase/entrypoint.ts | 61 +-- .../NewsfeedPaginationComponent/entrypoint.ts | 225 ++++---- .../__isograph/Query/HomePage/entrypoint.ts | 99 ++-- libs/isograph-react/src/core/cache.ts | 6 +- libs/isograph-react/src/core/check.ts | 6 +- libs/isograph-react/src/core/entrypoint.ts | 21 +- .../src/core/garbageCollection.ts | 6 +- libs/isograph-react/src/core/logging.ts | 4 +- .../src/core/makeNetworkRequest.ts | 7 +- .../__isograph/Query/meName/entrypoint.ts | 43 +- .../Query/meNameSuccessor/entrypoint.ts | 105 ++-- .../__isograph/Query/nodeField/entrypoint.ts | 51 +- .../__isograph/Query/subquery/entrypoint.ts | 67 +-- .../src/tests/garbageCollection.test.ts | 3 +- .../src/tests/meNameSuccessor.ts | 2 +- libs/isograph-react/src/tests/nodeQuery.ts | 3 +- .../src/tests/normalizeData.test.ts | 2 +- 45 files changed, 2930 insertions(+), 2813 deletions(-) diff --git a/crates/graphql_artifact_generation/src/entrypoint_artifact.rs b/crates/graphql_artifact_generation/src/entrypoint_artifact.rs index 2702ec2f8..0eeff4726 100644 --- a/crates/graphql_artifact_generation/src/entrypoint_artifact.rs +++ b/crates/graphql_artifact_generation/src/entrypoint_artifact.rs @@ -147,7 +147,7 @@ pub(crate) fn generate_entrypoint_artifacts_with_client_field_traversal_result<' generate_refetch_query_artifact_import(&refetch_paths_with_variables, file_extensions); let normalization_ast_text = - generate_normalization_ast_text(schema, merged_selection_map.values(), 0); + generate_normalization_ast_text(schema, merged_selection_map.values(), 1); let concrete_type = schema.server_field_data.object( if schema @@ -294,7 +294,10 @@ impl EntrypointArtifactInfo<'_> { import readerResolver from './{resolver_reader_file_name}{ts_file_extension}';\n\ {refetch_query_artifact_import}\n\n\ const queryText = '{query_text}';\n\n\ - const normalizationAst: NormalizationAst = {normalization_ast_text};\n\ + const normalizationAst: NormalizationAst = {{\n\ + {}kind: \"NormalizationAst\",\n\ + {}selections: {normalization_ast_text},\n\ + }};\n\ const artifact: IsographEntrypoint<\n\ {}{entrypoint_params_typename},\n\ {}{entrypoint_output_type_name}\n\ @@ -313,7 +316,7 @@ impl EntrypointArtifactInfo<'_> { {}}},\n\ }};\n\n\ export default artifact;\n", - " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", + " "," "," ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", ) } } diff --git a/crates/graphql_artifact_generation/src/imperatively_loaded_fields.rs b/crates/graphql_artifact_generation/src/imperatively_loaded_fields.rs index 6f2de199e..f71f25435 100644 --- a/crates/graphql_artifact_generation/src/imperatively_loaded_fields.rs +++ b/crates/graphql_artifact_generation/src/imperatively_loaded_fields.rs @@ -54,7 +54,10 @@ impl ImperativelyLoadedEntrypointArtifactInfo { format!( "import type {{ IsographEntrypoint, ReaderAst, FragmentReference, NormalizationAst, RefetchQueryNormalizationArtifact }} from '@isograph/react';\n\ const queryText = '{query_text}';\n\n\ - const normalizationAst: NormalizationAst = {normalization_ast};\n\ + const normalizationAst: NormalizationAst = {{\n\ + {}kind: \"NormalizationAst\",\n\ + {}selections: {normalization_ast},\n\ + }};\n\ const artifact: RefetchQueryNormalizationArtifact = {{\n\ {}kind: \"RefetchQuery\",\n\ {}networkRequestInfo: {{\n\ @@ -72,6 +75,8 @@ impl ImperativelyLoadedEntrypointArtifactInfo { " ", " ", " ", + " ", + " ", ) } @@ -103,7 +108,7 @@ pub(crate) fn get_artifact_for_imperatively_loaded_field( ); let normalization_ast_text = - generate_normalization_ast_text(schema, merged_selection_set.values(), 0); + generate_normalization_ast_text(schema, merged_selection_set.values(), 1); ImperativelyLoadedEntrypointArtifactInfo { normalization_ast_text, diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts index 6373b761a..28569e183 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/__refetch__0.ts @@ -38,183 +38,186 @@ const queryText = 'query User__refetch ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "User", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Literal", value: 10 }, - ], - - [ - "after", - { kind: "Literal", value: null }, - ], - ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "User", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Literal", value: 10 }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Literal", value: null }, ], - }, - ], - }, - ], - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts index 362f12223..66036f98f 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/HomePage/entrypoint.ts @@ -43,167 +43,170 @@ const queryText = 'query HomePage {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Literal", value: 10 }, - ], - - [ - "after", - { kind: "Literal", value: null }, - ], - ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Literal", value: 10 }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Literal", value: null }, ], - }, - ], - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__HomePage__param, Query__HomePage__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts index 75db112b5..1d34e6450 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/PullRequest/entrypoint.ts @@ -33,144 +33,147 @@ const queryText = 'query PullRequest ($repositoryOwner: String!, $repositoryName },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "repository", - arguments: [ - [ - "owner", - { kind: "Variable", name: "repositoryOwner" }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "repository", + arguments: [ + [ + "owner", + { kind: "Variable", name: "repositoryOwner" }, + ], - [ - "name", - { kind: "Variable", name: "repositoryName" }, - ], - ], - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "pullRequest", - arguments: [ - [ - "number", - { kind: "Variable", name: "pullRequestNumber" }, - ], + [ + "name", + { kind: "Variable", name: "repositoryName" }, ], - concreteType: "PullRequest", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "bodyHTML", - arguments: null, - }, - { - kind: "Linked", - fieldName: "comments", - arguments: [ - [ - "last", - { kind: "Literal", value: 10 }, - ], + ], + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "pullRequest", + arguments: [ + [ + "number", + { kind: "Variable", name: "pullRequestNumber" }, ], - concreteType: "IssueCommentConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "IssueCommentEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "IssueComment", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "author", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "bodyText", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "createdAt", - arguments: null, - }, - ], - }, + ], + concreteType: "PullRequest", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "bodyHTML", + arguments: null, + }, + { + kind: "Linked", + fieldName: "comments", + arguments: [ + [ + "last", + { kind: "Literal", value: 10 }, ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, - ], - }, - ], - }, - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + ], + concreteType: "IssueCommentConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "IssueCommentEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "IssueComment", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "author", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "bodyText", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "createdAt", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PullRequest__param, Query__PullRequest__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts index 6be9f97c9..05c8c2ce2 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/RepositoryPage/entrypoint.ts @@ -58,254 +58,257 @@ const queryText = 'query RepositoryPage ($repositoryName: String!, $repositoryOw },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "repository", - arguments: [ - [ - "name", - { kind: "Variable", name: "repositoryName" }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "repository", + arguments: [ + [ + "name", + { kind: "Variable", name: "repositoryName" }, + ], - [ - "owner", - { kind: "Variable", name: "repositoryOwner" }, - ], - ], - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "parent", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, + [ + "owner", + { kind: "Variable", name: "repositoryOwner" }, ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: [ - [ - "last", - { kind: "Variable", name: "first" }, + ], + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "parent", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, ], - ], - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "PullRequestEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "PullRequest", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "author", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - { - kind: "InlineFragment", - type: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "twitterUsername", - arguments: null, - }, - ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "closed", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "createdAt", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "number", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repository", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "totalCommentsCount", - arguments: null, - }, - ], - }, + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: [ + [ + "last", + { kind: "Variable", name: "first" }, ], - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "viewerHasStarred", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + ], + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "PullRequestEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "PullRequest", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "author", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + { + kind: "InlineFragment", + type: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "twitterUsername", + arguments: null, + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "closed", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "createdAt", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "number", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repository", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "totalCommentsCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "viewerHasStarred", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__RepositoryPage__param, Query__RepositoryPage__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts index 348b9cb3f..6727e887a 100644 --- a/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/Query/UserPage/entrypoint.ts @@ -43,185 +43,188 @@ const queryText = 'query UserPage ($userLogin: String!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "user", - arguments: [ - [ - "login", - { kind: "Variable", name: "userLogin" }, - ], - ], - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Literal", value: 10 }, - ], - - [ - "after", - { kind: "Literal", value: null }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "user", + arguments: [ + [ + "login", + { kind: "Variable", name: "userLogin" }, ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, + ], + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Literal", value: 10 }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Literal", value: null }, ], - }, - ], - }, - ], - }, - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "User", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "avatarUrl", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "User", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "avatarUrl", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__UserPage__param, Query__UserPage__output_type diff --git a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts index 1c2e894c5..a2a76eb57 100644 --- a/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts +++ b/demos/github-demo/src/isograph-components/__isograph/User/RepositoryConnection/entrypoint.ts @@ -40,168 +40,171 @@ const queryText = 'query RepositoryConnection ($first: Int, $after: String, $id: },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "User", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "repositories", - arguments: [ - [ - "first", - { kind: "Variable", name: "first" }, - ], - - [ - "after", - { kind: "Variable", name: "after" }, - ], - ], - concreteType: "RepositoryConnection", - selections: [ - { - kind: "Linked", - fieldName: "edges", - arguments: null, - concreteType: "RepositoryEdge", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: null, - concreteType: "Repository", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "description", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forkCount", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nameWithOwner", - arguments: null, - }, - { - kind: "Linked", - fieldName: "owner", - arguments: null, - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "login", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "pullRequests", - arguments: null, - concreteType: "PullRequestConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "stargazerCount", - arguments: null, - }, - { - kind: "Linked", - fieldName: "watchers", - arguments: null, - concreteType: "UserConnection", - selections: [ - { - kind: "Scalar", - fieldName: "totalCount", - arguments: null, - }, - ], - }, - ], - }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "User", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "repositories", + arguments: [ + [ + "first", + { kind: "Variable", name: "first" }, ], - }, - { - kind: "Linked", - fieldName: "pageInfo", - arguments: null, - concreteType: "PageInfo", - selections: [ - { - kind: "Scalar", - fieldName: "endCursor", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hasNextPage", - arguments: null, - }, + + [ + "after", + { kind: "Variable", name: "after" }, ], - }, - ], - }, - ], - }, - ], - }, -]; + ], + concreteType: "RepositoryConnection", + selections: [ + { + kind: "Linked", + fieldName: "edges", + arguments: null, + concreteType: "RepositoryEdge", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: null, + concreteType: "Repository", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "description", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forkCount", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nameWithOwner", + arguments: null, + }, + { + kind: "Linked", + fieldName: "owner", + arguments: null, + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "login", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "pullRequests", + arguments: null, + concreteType: "PullRequestConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "stargazerCount", + arguments: null, + }, + { + kind: "Linked", + fieldName: "watchers", + arguments: null, + concreteType: "UserConnection", + selections: [ + { + kind: "Scalar", + fieldName: "totalCount", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + { + kind: "Linked", + fieldName: "pageInfo", + arguments: null, + concreteType: "PageInfo", + selections: [ + { + kind: "Scalar", + fieldName: "endCursor", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hasNextPage", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< User__RepositoryConnection__param, User__RepositoryConnection__output_type diff --git a/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts b/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts index 4e09394ab..a0c1928fd 100644 --- a/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/AdItem/AdItemDisplay/entrypoint.ts @@ -15,47 +15,50 @@ const queryText = 'query AdItemDisplay ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "AdItem", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "advertiser", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "message", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "AdItem", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "advertiser", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "message", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< AdItem__AdItemDisplay__param, AdItem__AdItemDisplay__output_type diff --git a/demos/pet-demo/src/components/__isograph/BlogItem/BlogItemMoreDetail/entrypoint.ts b/demos/pet-demo/src/components/__isograph/BlogItem/BlogItemMoreDetail/entrypoint.ts index 105ff064f..e725426d6 100644 --- a/demos/pet-demo/src/components/__isograph/BlogItem/BlogItemMoreDetail/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/BlogItem/BlogItemMoreDetail/entrypoint.ts @@ -14,42 +14,45 @@ const queryText = 'query BlogItemMoreDetail ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "BlogItem", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "moreContent", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "BlogItem", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "moreContent", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< BlogItem__BlogItemMoreDetail__param, BlogItem__BlogItemMoreDetail__output_type diff --git a/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts index 369e9373c..0040205e4 100644 --- a/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Image/ImageDisplay/entrypoint.ts @@ -14,42 +14,45 @@ const queryText = 'query ImageDisplay ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Image", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "url", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Image", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "url", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Image__ImageDisplay__param, Image__ImageDisplay__output_type diff --git a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts index 054f5b224..bea919c28 100644 --- a/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Mutation/SetTagline/entrypoint.ts @@ -13,39 +13,42 @@ const queryText = 'mutation SetTagline ($input: SetPetTaglineParams!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "set_pet_tagline", - arguments: [ - [ - "input", - { kind: "Variable", name: "input" }, - ], - ], - concreteType: "SetPetTaglineResponse", - selections: [ - { - kind: "Linked", - fieldName: "pet", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "set_pet_tagline", + arguments: [ + [ + "input", + { kind: "Variable", name: "input" }, ], - }, - ], - }, -]; + ], + concreteType: "SetPetTaglineResponse", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Mutation__SetTagline__param, Mutation__SetTagline__output_type diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts index d97fd72bc..ee3626fde 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/__refetch__0.ts @@ -12,55 +12,58 @@ const queryText = 'mutation Pet__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts index dfd7fc509..3c9235f73 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCard/entrypoint.ts @@ -21,70 +21,73 @@ const queryText = 'query PetCheckinsCard ($skip: Int, $limit: Int, $id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Variable", name: "skip" }, - ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Variable", name: "skip" }, + ], - [ - "limit", - { kind: "Variable", name: "limit" }, + [ + "limit", + { kind: "Variable", name: "limit" }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Pet__PetCheckinsCard__param, Pet__PetCheckinsCard__output_type diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts index d97fd72bc..ee3626fde 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/__refetch__0.ts @@ -12,55 +12,58 @@ const queryText = 'mutation Pet__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts index 4de5ac267..820837603 100644 --- a/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Pet/PetCheckinsCardList/entrypoint.ts @@ -21,70 +21,73 @@ const queryText = 'query PetCheckinsCardList ($skip: Int!, $limit: Int!, $id: ID },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Variable", name: "skip" }, - ], + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Variable", name: "skip" }, + ], - [ - "limit", - { kind: "Variable", name: "limit" }, + [ + "limit", + { kind: "Variable", name: "limit" }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Pet__PetCheckinsCardList__param, Pet__PetCheckinsCardList__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts index dcfe8ae2e..956cda1ac 100644 --- a/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/HomeRoute/entrypoint.ts @@ -13,36 +13,39 @@ const queryText = 'query HomeRoute {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pets", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, -]; +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pets", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__HomeRoute__param, Query__HomeRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts index d2414e400..cb47034e2 100644 --- a/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/Newsfeed/entrypoint.ts @@ -27,104 +27,107 @@ const queryText = 'query Newsfeed {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "viewer", - arguments: null, - concreteType: "Viewer", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "newsfeed", - arguments: [ - [ - "skip", - { kind: "Literal", value: 0 }, - ], - - [ - "limit", - { kind: "Literal", value: 6 }, - ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "InlineFragment", - type: "AdItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "viewer", + arguments: null, + concreteType: "Viewer", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "newsfeed", + arguments: [ + [ + "skip", + { kind: "Literal", value: 0 }, ], - }, - { - kind: "InlineFragment", - type: "BlogItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "author", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "content", - arguments: null, - }, - { - kind: "Linked", - fieldName: "image", - arguments: null, - concreteType: "Image", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, + + [ + "limit", + { kind: "Literal", value: 6 }, ], - }, - ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "InlineFragment", + type: "AdItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + ], + }, + { + kind: "InlineFragment", + type: "BlogItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "author", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "content", + arguments: null, + }, + { + kind: "Linked", + fieldName: "image", + arguments: null, + concreteType: "Image", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__Newsfeed__param, Query__Newsfeed__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts index b70ba5441..e56220884 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetByName/entrypoint.ts @@ -11,31 +11,34 @@ const queryText = 'query PetByName ($name: String!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "petByName", - arguments: [ - [ - "name", - { kind: "Variable", name: "name" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "petByName", + arguments: [ + [ + "name", + { kind: "Variable", name: "name" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetByName__param, Query__PetByName__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts index b73c63ca6..c6775a614 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/__refetch__0.ts @@ -11,50 +11,53 @@ const queryText = 'mutation Query__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts index 2bc40039a..545e3555b 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetCheckinListRoute/entrypoint.ts @@ -18,59 +18,62 @@ const queryText = 'query PetCheckinListRoute ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: 0 }, - ], + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: 0 }, + ], - [ - "limit", - { kind: "Literal", value: 1 }, + [ + "limit", + { kind: "Literal", value: 1 }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetCheckinListRoute__param, Query__PetCheckinListRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts index 15d46f99d..ca1481b53 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailDeferredRoute/entrypoint.ts @@ -11,31 +11,34 @@ const queryText = 'query PetDetailDeferredRoute ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetDetailDeferredRoute__param, Query__PetDetailDeferredRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts index 52a40343e..56ae2104b 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__0.ts @@ -38,187 +38,190 @@ const queryText = 'query Pet__refetch ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts index 512986f9c..92d7c8df1 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__1.ts @@ -37,189 +37,192 @@ const queryText = 'mutation Query__set_best_friend ($id: ID!, $new_best_friend_i },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "set_pet_best_friend", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "set_pet_best_friend", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], - [ - "new_best_friend_id", - { kind: "Variable", name: "new_best_friend_id" }, + [ + "new_best_friend_id", + { kind: "Variable", name: "new_best_friend_id" }, + ], ], - ], - concreteType: "SetBestFriendResponse", - selections: [ - { - kind: "Linked", - fieldName: "pet", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + concreteType: "SetBestFriendResponse", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts index f76fd47f0..135103484 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__2.ts @@ -37,184 +37,187 @@ const queryText = 'mutation Query__set_pet_tagline ($input: SetPetTaglineParams! },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "set_pet_tagline", - arguments: [ - [ - "input", - { kind: "Variable", name: "input" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "set_pet_tagline", + arguments: [ + [ + "input", + { kind: "Variable", name: "input" }, + ], ], - ], - concreteType: "SetPetTaglineResponse", - selections: [ - { - kind: "Linked", - fieldName: "pet", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + concreteType: "SetPetTaglineResponse", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts index 0c8c993b9..8ea84f92b 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__3.ts @@ -12,55 +12,58 @@ const queryText = 'mutation Query__make_super ($checkin_id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "make_checkin_super", - arguments: [ - [ - "checkin_id", - { kind: "Variable", name: "checkin_id" }, - ], - ], - concreteType: "MakeCheckinSuperResponse", - selections: [ - { - kind: "Linked", - fieldName: "checkin", - arguments: null, - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "make_checkin_super", + arguments: [ + [ + "checkin_id", + { kind: "Variable", name: "checkin_id" }, ], - }, - ], - }, -]; + ], + concreteType: "MakeCheckinSuperResponse", + selections: [ + { + kind: "Linked", + fieldName: "checkin", + arguments: null, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts index 0dd86e038..41f1ffe99 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/__refetch__4.ts @@ -12,59 +12,62 @@ const queryText = 'query Query__refetch_pet_stats ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - ], - }, -]; + ], + concreteType: "Pet", + selections: [ + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: RefetchQueryNormalizationArtifact = { kind: "RefetchQuery", networkRequestInfo: { diff --git a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts index d61e78034..575e34a13 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetDetailRoute/entrypoint.ts @@ -51,176 +51,179 @@ const queryText = 'query PetDetailRoute ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, - ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "age", - arguments: null, - }, - { - kind: "Linked", - fieldName: "best_friend_relationship", - arguments: null, - concreteType: "BestFriendRelationship", - selections: [ - { - kind: "Linked", - fieldName: "best_friend", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "picture", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "picture_together", - arguments: null, - }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, ], - }, - { - kind: "Linked", - fieldName: "checkins", - arguments: [ - [ - "skip", - { kind: "Literal", value: null }, + ], + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "age", + arguments: null, + }, + { + kind: "Linked", + fieldName: "best_friend_relationship", + arguments: null, + concreteType: "BestFriendRelationship", + selections: [ + { + kind: "Linked", + fieldName: "best_friend", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "picture", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "picture_together", + arguments: null, + }, ], + }, + { + kind: "Linked", + fieldName: "checkins", + arguments: [ + [ + "skip", + { kind: "Literal", value: null }, + ], - [ - "limit", - { kind: "Literal", value: null }, + [ + "limit", + { kind: "Literal", value: null }, + ], ], - ], - concreteType: "Checkin", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "location", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "time", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "nickname", - arguments: null, - }, - { - kind: "Linked", - fieldName: "potential_new_best_friends", - arguments: null, - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - { - kind: "Linked", - fieldName: "stats", - arguments: null, - concreteType: "PetStats", - selections: [ - { - kind: "Scalar", - fieldName: "cuteness", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "energy", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "hunger", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "intelligence", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sociability", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "weight", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "tagline", - arguments: null, - }, - ], - }, -]; + concreteType: "Checkin", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "location", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "time", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "nickname", + arguments: null, + }, + { + kind: "Linked", + fieldName: "potential_new_best_friends", + arguments: null, + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + { + kind: "Linked", + fieldName: "stats", + arguments: null, + concreteType: "PetStats", + selections: [ + { + kind: "Scalar", + fieldName: "cuteness", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "energy", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "hunger", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "intelligence", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sociability", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "weight", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "tagline", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetDetailRoute__param, Query__PetDetailRoute__output_type diff --git a/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts index 4aee60bb5..7590f7880 100644 --- a/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Query/PetFavoritePhrase/entrypoint.ts @@ -12,36 +12,39 @@ const queryText = 'query PetFavoritePhrase ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "pet", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "pet", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: "Pet", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "favorite_phrase", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; + concreteType: "Pet", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "favorite_phrase", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__PetFavoritePhrase__param, Query__PetFavoritePhrase__output_type diff --git a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts index c5d2f1aac..fd8db8331 100644 --- a/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts +++ b/demos/pet-demo/src/components/__isograph/Viewer/NewsfeedPaginationComponent/entrypoint.ts @@ -30,120 +30,123 @@ const queryText = 'query NewsfeedPaginationComponent ($skip: Int!, $limit: Int!, },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "InlineFragment", - type: "Viewer", - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "newsfeed", - arguments: [ - [ - "skip", - { kind: "Variable", name: "skip" }, - ], - - [ - "limit", - { kind: "Variable", name: "limit" }, - ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "InlineFragment", - type: "AdItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, + concreteType: null, + selections: [ + { + kind: "InlineFragment", + type: "Viewer", + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "newsfeed", + arguments: [ + [ + "skip", + { kind: "Variable", name: "skip" }, ], - }, - { - kind: "InlineFragment", - type: "BlogItem", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "author", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "content", - arguments: null, - }, - { - kind: "Linked", - fieldName: "image", - arguments: null, - concreteType: "Image", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, - { - kind: "Scalar", - fieldName: "title", - arguments: null, - }, + + [ + "limit", + { kind: "Variable", name: "limit" }, ], - }, - ], - }, - ], - }, - ], - }, -]; + ], + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "InlineFragment", + type: "AdItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + ], + }, + { + kind: "InlineFragment", + type: "BlogItem", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "author", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "content", + arguments: null, + }, + { + kind: "Linked", + fieldName: "image", + arguments: null, + concreteType: "Image", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + { + kind: "Scalar", + fieldName: "title", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Viewer__NewsfeedPaginationComponent__param, Viewer__NewsfeedPaginationComponent__output_type diff --git a/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts b/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts index 7667834a2..0c8065a5a 100644 --- a/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts +++ b/demos/vite-demo/src/components/__isograph/Query/HomePage/entrypoint.ts @@ -15,56 +15,59 @@ const queryText = 'query HomePage {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "getAllPokemon", - arguments: [ - [ - "take", - { kind: "Literal", value: 232 }, - ], +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "getAllPokemon", + arguments: [ + [ + "take", + { kind: "Literal", value: 232 }, + ], - [ - "offset", - { kind: "Literal", value: 93 }, + [ + "offset", + { kind: "Literal", value: 93 }, + ], ], - ], - concreteType: "Pokemon", - selections: [ - { - kind: "Scalar", - fieldName: "bulbapediaPage", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "forme", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "key", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "num", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "species", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "sprite", - arguments: null, - }, - ], - }, -]; + concreteType: "Pokemon", + selections: [ + { + kind: "Scalar", + fieldName: "bulbapediaPage", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "forme", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "key", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "num", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "species", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "sprite", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__HomePage__param, Query__HomePage__output_type diff --git a/libs/isograph-react/src/core/cache.ts b/libs/isograph-react/src/core/cache.ts index 088331e53..7c3430d26 100644 --- a/libs/isograph-react/src/core/cache.ts +++ b/libs/isograph-react/src/core/cache.ts @@ -16,7 +16,7 @@ import { } from './IsographEnvironment'; import { IsographEntrypoint, - NormalizationAst, + type NormalizationAstNodes, NormalizationInlineFragment, NormalizationLinkedField, NormalizationScalarField, @@ -142,7 +142,7 @@ export type NetworkResponseObject = { export function normalizeData( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, networkResponse: NetworkResponseObject, variables: Variables, nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[], @@ -376,7 +376,7 @@ export type EncounteredIds = Map>; */ function normalizeDataIntoRecord( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, networkResponseParentRecord: NetworkResponseObject, targetParentRecord: StoreRecord, targetParentRecordLink: Link, diff --git a/libs/isograph-react/src/core/check.ts b/libs/isograph-react/src/core/check.ts index 1e7d0b210..6ed25c0a4 100644 --- a/libs/isograph-react/src/core/check.ts +++ b/libs/isograph-react/src/core/check.ts @@ -1,5 +1,5 @@ import { getParentRecordKey } from './cache'; -import { NormalizationAst } from './entrypoint'; +import { NormalizationAstNodes } from './entrypoint'; import { Variables } from './FragmentReference'; import { getLink, @@ -30,7 +30,7 @@ export type CheckResult = export function check( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, variables: Variables, root: Link, ): CheckResult { @@ -53,7 +53,7 @@ export function check( function checkFromRecord( environment: IsographEnvironment, - normalizationAst: NormalizationAst, + normalizationAst: NormalizationAstNodes, variables: Variables, record: StoreRecord, recordLink: Link, diff --git a/libs/isograph-react/src/core/entrypoint.ts b/libs/isograph-react/src/core/entrypoint.ts index 694fc7286..a935ef0d8 100644 --- a/libs/isograph-react/src/core/entrypoint.ts +++ b/libs/isograph-react/src/core/entrypoint.ts @@ -16,18 +16,19 @@ export type ReaderWithRefetchQueries< readonly nestedRefetchQueries: RefetchQueryNormalizationArtifactWrapper[]; }; -export type NetworkRequestInfo = { +export type NetworkRequestInfo = { readonly kind: 'NetworkRequestInfo'; readonly queryText: string; - readonly normalizationAst: NormalizationAst; + readonly normalizationAst: TNormalizationAst; }; // This type should be treated as an opaque type. export type IsographEntrypoint< TReadFromStore extends { parameters: object; data: object }, TClientFieldValue, + TNormalizationAst = NormalizationAst, > = { readonly kind: 'Entrypoint'; - readonly networkRequestInfo: NetworkRequestInfo; + readonly networkRequestInfo: NetworkRequestInfo; readonly readerWithRefetchQueries: ReaderWithRefetchQueries< TReadFromStore, TClientFieldValue @@ -50,7 +51,13 @@ export type NormalizationAstNode = | NormalizationScalarField | NormalizationLinkedField | NormalizationInlineFragment; -export type NormalizationAst = ReadonlyArray; + +export type NormalizationAstNodes = ReadonlyArray; + +export type NormalizationAst = { + kind: 'NormalizationAst'; + selections: NormalizationAstNodes; +}; export type NormalizationScalarField = { readonly kind: 'Scalar'; @@ -62,20 +69,20 @@ export type NormalizationLinkedField = { readonly kind: 'Linked'; readonly fieldName: string; readonly arguments: Arguments | null; - readonly selections: NormalizationAst; + readonly selections: NormalizationAstNodes; readonly concreteType: TypeName | null; }; export type NormalizationInlineFragment = { readonly kind: 'InlineFragment'; readonly type: string; - readonly selections: NormalizationAst; + readonly selections: NormalizationAstNodes; }; // This is more like an entrypoint, but one specifically for a refetch query/mutation export type RefetchQueryNormalizationArtifact = { readonly kind: 'RefetchQuery'; - readonly networkRequestInfo: NetworkRequestInfo; + readonly networkRequestInfo: NetworkRequestInfo; readonly concreteType: TypeName; }; diff --git a/libs/isograph-react/src/core/garbageCollection.ts b/libs/isograph-react/src/core/garbageCollection.ts index 1442a179d..4a6ceac81 100644 --- a/libs/isograph-react/src/core/garbageCollection.ts +++ b/libs/isograph-react/src/core/garbageCollection.ts @@ -9,10 +9,10 @@ import { type TypeName, } from './IsographEnvironment'; import { getParentRecordKey } from './cache'; -import { NormalizationAst } from './entrypoint'; +import { NormalizationAstNodes } from './entrypoint'; export type RetainedQuery = { - readonly normalizationAst: NormalizationAst; + readonly normalizationAst: NormalizationAstNodes; readonly variables: {}; readonly root: Link; }; @@ -108,7 +108,7 @@ function recordReachableIdsFromRecord( store: IsographStore, currentRecord: StoreRecord, mutableRetainedIds: RetainedIds, - selections: NormalizationAst, + selections: NormalizationAstNodes, variables: Variables | null, ) { for (const selection of selections) { diff --git a/libs/isograph-react/src/core/logging.ts b/libs/isograph-react/src/core/logging.ts index a19610bb0..5e9ace9e3 100644 --- a/libs/isograph-react/src/core/logging.ts +++ b/libs/isograph-react/src/core/logging.ts @@ -7,7 +7,7 @@ import { } from './IsographEnvironment'; import { IsographEntrypoint, - NormalizationAst, + type NormalizationAstNodes, RefetchQueryNormalizationArtifact, } from './entrypoint'; import { FragmentReference, Variables } from './FragmentReference'; @@ -25,7 +25,7 @@ export type LogMessage = } | { kind: 'AboutToNormalize'; - normalizationAst: NormalizationAst; + normalizationAst: NormalizationAstNodes; networkResponse: NetworkResponseObject; variables: Variables; } diff --git a/libs/isograph-react/src/core/makeNetworkRequest.ts b/libs/isograph-react/src/core/makeNetworkRequest.ts index 18957eb24..b29cd68eb 100644 --- a/libs/isograph-react/src/core/makeNetworkRequest.ts +++ b/libs/isograph-react/src/core/makeNetworkRequest.ts @@ -46,7 +46,7 @@ export function maybeMakeNetworkRequest< case 'IfNecessary': { const result = check( environment, - artifact.networkRequestInfo.normalizationAst, + artifact.networkRequestInfo.normalizationAst.selections, variables, { __link: ROOT_ID, @@ -116,7 +116,7 @@ export function makeNetworkRequest< if (status.kind === 'UndisposedIncomplete') { normalizeData( environment, - artifact.networkRequestInfo.normalizationAst, + artifact.networkRequestInfo.normalizationAst.selections, networkResponse.data ?? {}, variables, artifact.kind === 'Entrypoint' @@ -125,7 +125,8 @@ export function makeNetworkRequest< root, ); const retainedQuery = { - normalizationAst: artifact.networkRequestInfo.normalizationAst, + normalizationAst: + artifact.networkRequestInfo.normalizationAst.selections, variables, root, }; diff --git a/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts index 81294fca7..deea61e36 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/meName/entrypoint.ts @@ -11,26 +11,29 @@ const queryText = 'query meName {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "me", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, -]; +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "me", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__meName__param, Query__meName__output_type diff --git a/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts index c1262b196..a2e836a55 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/meNameSuccessor/entrypoint.ts @@ -18,57 +18,60 @@ const queryText = 'query meNameSuccessor {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "me", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - { - kind: "Linked", - fieldName: "successor", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Linked", - fieldName: "successor", - arguments: null, - concreteType: "Economist", - selections: [ - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "name", - arguments: null, - }, - ], - }, - ], - }, - ], - }, -]; +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "me", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + { + kind: "Linked", + fieldName: "successor", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Linked", + fieldName: "successor", + arguments: null, + concreteType: "Economist", + selections: [ + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "name", + arguments: null, + }, + ], + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__meNameSuccessor__param, Query__meNameSuccessor__output_type diff --git a/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts index 083b2d325..c012c1a0c 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/nodeField/entrypoint.ts @@ -11,31 +11,34 @@ const queryText = 'query nodeField ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, -]; + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__nodeField__param, Query__nodeField__output_type diff --git a/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts b/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts index e22e746b1..fceffcb0f 100644 --- a/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts +++ b/libs/isograph-react/src/tests/__isograph/Query/subquery/entrypoint.ts @@ -13,39 +13,42 @@ const queryText = 'query subquery ($id: ID!) {\ },\ }'; -const normalizationAst: NormalizationAst = [ - { - kind: "Linked", - fieldName: "query", - arguments: null, - concreteType: "Query", - selections: [ - { - kind: "Linked", - fieldName: "node", - arguments: [ - [ - "id", - { kind: "Variable", name: "id" }, +const normalizationAst: NormalizationAst = { + kind: "NormalizationAst", + selections: [ + { + kind: "Linked", + fieldName: "query", + arguments: null, + concreteType: "Query", + selections: [ + { + kind: "Linked", + fieldName: "node", + arguments: [ + [ + "id", + { kind: "Variable", name: "id" }, + ], ], - ], - concreteType: null, - selections: [ - { - kind: "Scalar", - fieldName: "__typename", - arguments: null, - }, - { - kind: "Scalar", - fieldName: "id", - arguments: null, - }, - ], - }, - ], - }, -]; + concreteType: null, + selections: [ + { + kind: "Scalar", + fieldName: "__typename", + arguments: null, + }, + { + kind: "Scalar", + fieldName: "id", + arguments: null, + }, + ], + }, + ], + }, + ], +}; const artifact: IsographEntrypoint< Query__subquery__param, Query__subquery__output_type diff --git a/libs/isograph-react/src/tests/garbageCollection.test.ts b/libs/isograph-react/src/tests/garbageCollection.test.ts index ba6e9cfd5..25310bde5 100644 --- a/libs/isograph-react/src/tests/garbageCollection.test.ts +++ b/libs/isograph-react/src/tests/garbageCollection.test.ts @@ -55,7 +55,8 @@ export const meNameField = iso(` import { meNameSuccessorRetainedQuery } from './meNameSuccessor'; const meNameEntrypoint = iso(`entrypoint Query.meName`); const meNameRetainedQuery = { - normalizationAst: meNameEntrypoint.networkRequestInfo.normalizationAst, + normalizationAst: + meNameEntrypoint.networkRequestInfo.normalizationAst.selections, variables: {}, root: { __link: ROOT_ID, __typename: 'Query' }, }; diff --git a/libs/isograph-react/src/tests/meNameSuccessor.ts b/libs/isograph-react/src/tests/meNameSuccessor.ts index b3267816e..c32290f6b 100644 --- a/libs/isograph-react/src/tests/meNameSuccessor.ts +++ b/libs/isograph-react/src/tests/meNameSuccessor.ts @@ -16,7 +16,7 @@ export const meNameField = iso(` const meNameSuccessorEntrypoint = iso(`entrypoint Query.meNameSuccessor`); export const meNameSuccessorRetainedQuery = { normalizationAst: - meNameSuccessorEntrypoint.networkRequestInfo.normalizationAst, + meNameSuccessorEntrypoint.networkRequestInfo.normalizationAst.selections, variables: {}, root: { __link: ROOT_ID, diff --git a/libs/isograph-react/src/tests/nodeQuery.ts b/libs/isograph-react/src/tests/nodeQuery.ts index 0dd93b522..c3e362a0f 100644 --- a/libs/isograph-react/src/tests/nodeQuery.ts +++ b/libs/isograph-react/src/tests/nodeQuery.ts @@ -13,7 +13,8 @@ export const nodeField = iso(` `)(() => {}); const nodeFieldEntrypoint = iso(`entrypoint Query.nodeField`); export const nodeFieldRetainedQuery: RetainedQuery = { - normalizationAst: nodeFieldEntrypoint.networkRequestInfo.normalizationAst, + normalizationAst: + nodeFieldEntrypoint.networkRequestInfo.normalizationAst.selections, variables: { id: 0 }, root: { __link: ROOT_ID, __typename: 'Query' }, }; diff --git a/libs/isograph-react/src/tests/normalizeData.test.ts b/libs/isograph-react/src/tests/normalizeData.test.ts index 7e7a846d1..b323fcf37 100644 --- a/libs/isograph-react/src/tests/normalizeData.test.ts +++ b/libs/isograph-react/src/tests/normalizeData.test.ts @@ -35,7 +35,7 @@ describe('normalizeData', () => { normalizeData( environment, - entrypoint.networkRequestInfo.normalizationAst, + entrypoint.networkRequestInfo.normalizationAst.selections, { query: { node____id___v_id: { __typename: 'Economist', id: '1' } }, }, From ecc746795669fa7f3e23d099665ce460838e0fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Sat, 28 Dec 2024 09:38:16 +0100 Subject: [PATCH 49/61] no_babel_transform option & change entrypoint iso overloads to return void (#286) - add a no_babel_transform option to the config - if this is set to true, then iso literals for entrypoints have type void (even though they will actually be a x => x) - if this is set to true, then iso literals that are called (e.g. field definitions) will not throw an error - this gives a better overall experience to folks that cannot use the babel transform --- .../src/generate_artifacts.rs | 6 +- .../src/iso_overload_file.rs | 72 +++++++++++-------- .../isograph_compiler/src/compiler_state.rs | 14 ++-- .../src/compilation_options.rs | 8 +-- .../src/isograph-components/__isograph/iso.ts | 3 +- .../pet-demo/src/components/__isograph/iso.ts | 3 +- .../src/components/__isograph/iso.ts | 3 +- .../isograph-config-schema.json | 7 +- .../src/tests/__isograph/iso.ts | 3 +- 9 files changed, 66 insertions(+), 53 deletions(-) diff --git a/crates/graphql_artifact_generation/src/generate_artifacts.rs b/crates/graphql_artifact_generation/src/generate_artifacts.rs index 4db9e470f..1db43c6d4 100644 --- a/crates/graphql_artifact_generation/src/generate_artifacts.rs +++ b/crates/graphql_artifact_generation/src/generate_artifacts.rs @@ -7,7 +7,7 @@ use graphql_lang_types::{ }; use intern::{string_key::Intern, Lookup}; -use isograph_config::{GenerateFileExtensionsOption, OptionalValidationLevel}; +use isograph_config::GenerateFileExtensionsOption; use isograph_lang_types::{ ArgumentKeyAndValue, ClientFieldId, NonConstantValue, SelectableServerFieldId, SelectionType, ServerFieldSelection, TypeAnnotation, UnionVariant, VariableDefinition, @@ -79,7 +79,7 @@ pub fn get_artifact_path_and_content( project_root: &Path, artifact_directory: &Path, file_extensions: GenerateFileExtensionsOption, - on_missing_babel_transform: OptionalValidationLevel, + no_babel_transform: bool, ) -> Vec { let mut encountered_client_field_map = BTreeMap::new(); let mut path_and_contents = vec![]; @@ -309,7 +309,7 @@ pub fn get_artifact_path_and_content( path_and_contents.push(build_iso_overload_artifact( schema, file_extensions, - on_missing_babel_transform, + no_babel_transform, )); path_and_contents diff --git a/crates/graphql_artifact_generation/src/iso_overload_file.rs b/crates/graphql_artifact_generation/src/iso_overload_file.rs index 3a04edb99..5543e1aee 100644 --- a/crates/graphql_artifact_generation/src/iso_overload_file.rs +++ b/crates/graphql_artifact_generation/src/iso_overload_file.rs @@ -1,5 +1,5 @@ use intern::Lookup; -use isograph_config::{GenerateFileExtensionsOption, OptionalValidationLevel}; +use isograph_config::GenerateFileExtensionsOption; use std::{cmp::Ordering, path::PathBuf}; use common_lang_types::{ArtifactPathAndContent, SelectableFieldName}; @@ -13,29 +13,45 @@ use crate::generate_artifacts::ISO_TS; fn build_iso_overload_for_entrypoint( validated_client_field: &ValidatedClientField, file_extensions: GenerateFileExtensionsOption, -) -> (String, String) { - let mut s: String = "".to_string(); - let import = format!( - "import entrypoint_{} from '../__isograph/{}/{}/entrypoint{}';\n", - validated_client_field.type_and_field.underscore_separated(), - validated_client_field.type_and_field.type_name, - validated_client_field.type_and_field.field_name, - file_extensions.ts() - ); + no_babel_transform: bool, +) -> (Option, String) { let formatted_field = format!( "entrypoint {}.{}", validated_client_field.type_and_field.type_name, validated_client_field.type_and_field.field_name ); - s.push_str(&format!( - " + match no_babel_transform { + true => ( + None, + format!( + " +export function iso( + param: T & MatchesWhitespaceAndString<'{}', T> +): void;\n", + formatted_field + ), + ), + false => { + let mut s: String = "".to_string(); + let import = format!( + "import entrypoint_{} from '../__isograph/{}/{}/entrypoint{}';\n", + validated_client_field.type_and_field.underscore_separated(), + validated_client_field.type_and_field.type_name, + validated_client_field.type_and_field.field_name, + file_extensions.ts() + ); + + s.push_str(&format!( + " export function iso( param: T & MatchesWhitespaceAndString<'{}', T> ): typeof entrypoint_{};\n", - formatted_field, - validated_client_field.type_and_field.underscore_separated(), - )); - (import, s) + formatted_field, + validated_client_field.type_and_field.underscore_separated(), + )); + (Some(import), s) + } + } } fn build_iso_overload_for_client_defined_field( @@ -80,7 +96,7 @@ export function iso( pub(crate) fn build_iso_overload_artifact( schema: &ValidatedSchema, file_extensions: GenerateFileExtensionsOption, - on_missing_babel_transform: OptionalValidationLevel, + no_babel_transform: bool, ) -> ArtifactPathAndContent { let mut imports = "import type { IsographEntrypoint } from '@isograph/react';\n".to_string(); let mut content = String::from( @@ -144,9 +160,11 @@ type MatchesWhitespaceAndString< let entrypoint_overloads = sorted_entrypoints(schema) .into_iter() - .map(|field| build_iso_overload_for_entrypoint(field, file_extensions)); + .map(|field| build_iso_overload_for_entrypoint(field, file_extensions, no_babel_transform)); for (import, entrypoint_overload) in entrypoint_overloads { - imports.push_str(&import); + if let Some(import) = import { + imports.push_str(&import); + } content.push_str(&entrypoint_overload); } @@ -159,20 +177,14 @@ export function iso(_isographLiteralText: string): {\n", ); - content.push_str(match on_missing_babel_transform { - OptionalValidationLevel::Error => { + content.push_str(match no_babel_transform { + false => { " throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.');" - } - OptionalValidationLevel::Warn => { - - " console.warn('iso: Unexpected invocation at runtime. Either the Babel transform ' + - 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); - return (clientFieldResolver: any) => clientFieldResolver;" + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. ');" } - OptionalValidationLevel::Ignore => { + true => { " return (clientFieldResolver: any) => clientFieldResolver;" } }); diff --git a/crates/isograph_compiler/src/compiler_state.rs b/crates/isograph_compiler/src/compiler_state.rs index 1095faed8..d6affede5 100644 --- a/crates/isograph_compiler/src/compiler_state.rs +++ b/crates/isograph_compiler/src/compiler_state.rs @@ -1,9 +1,7 @@ use std::path::PathBuf; use graphql_artifact_generation::get_artifact_path_and_content; -use isograph_config::{ - create_config, CompilerConfig, GenerateFileExtensionsOption, OptionalValidationLevel, -}; +use isograph_config::{create_config, CompilerConfig, GenerateFileExtensionsOption}; use isograph_schema::{Schema, UnvalidatedSchema}; use crate::{ @@ -85,7 +83,7 @@ impl CompilerState { source_files, &self.config, self.config.options.generate_file_extensions, - self.config.options.on_missing_babel_transform, + self.config.options.no_babel_transform, )?; Ok(CompilationStats { client_field_count: stats.client_field_count, @@ -102,7 +100,7 @@ impl CompilerState { source_files, &self.config, self.config.options.generate_file_extensions, - self.config.options.on_missing_babel_transform, + self.config.options.no_babel_transform, )?; Ok(CompilationStats { client_field_count: stats.client_field_count, @@ -121,7 +119,7 @@ impl CompilerState { source_files, &self.config, self.config.options.generate_file_extensions, - self.config.options.on_missing_babel_transform, + self.config.options.no_babel_transform, )?; Ok(CompilationStats { client_field_count: stats.client_field_count, @@ -152,7 +150,7 @@ pub fn validate_and_create_artifacts_from_source_files( source_files: SourceFiles, config: &CompilerConfig, file_extensions: GenerateFileExtensionsOption, - on_missing_babel_transform: OptionalValidationLevel, + no_babel_transform: bool, ) -> Result { // Create schema let mut unvalidated_schema = UnvalidatedSchema::new(); @@ -169,7 +167,7 @@ pub fn validate_and_create_artifacts_from_source_files( &config.project_root, &config.artifact_directory, file_extensions, - on_missing_babel_transform, + no_babel_transform, ); let total_artifacts_written = write_artifacts_to_disk(artifacts, &config.artifact_directory)?; diff --git a/crates/isograph_config/src/compilation_options.rs b/crates/isograph_config/src/compilation_options.rs index 06e4c10ee..71ceb3470 100644 --- a/crates/isograph_config/src/compilation_options.rs +++ b/crates/isograph_config/src/compilation_options.rs @@ -28,7 +28,7 @@ pub struct CompilerConfig { #[derive(Default, Debug, Clone, Copy)] pub struct ConfigOptions { pub on_invalid_id_type: OptionalValidationLevel, - pub on_missing_babel_transform: OptionalValidationLevel, + pub no_babel_transform: bool, pub generate_file_extensions: GenerateFileExtensionsOption, } @@ -188,7 +188,7 @@ pub fn create_config(config_location: PathBuf) -> CompilerConfig { #[serde(default, deny_unknown_fields)] pub struct ConfigFileOptions { on_invalid_id_type: ConfigFileOptionalValidationLevel, - on_missing_babel_transform: ConfigFileOptionalValidationLevel, + no_babel_transform: bool, include_file_extensions_in_import_statements: bool, } @@ -212,9 +212,7 @@ impl Default for ConfigFileOptionalValidationLevel { fn create_options(options: ConfigFileOptions) -> ConfigOptions { ConfigOptions { on_invalid_id_type: create_optional_validation_level(options.on_invalid_id_type), - on_missing_babel_transform: create_optional_validation_level( - options.on_missing_babel_transform, - ), + no_babel_transform: options.no_babel_transform, generate_file_extensions: create_generate_file_extensions( options.include_file_extensions_in_import_statements, ), diff --git a/demos/github-demo/src/isograph-components/__isograph/iso.ts b/demos/github-demo/src/isograph-components/__isograph/iso.ts index b9cc634c2..85346833b 100644 --- a/demos/github-demo/src/isograph-components/__isograph/iso.ts +++ b/demos/github-demo/src/isograph-components/__isograph/iso.ts @@ -180,5 +180,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file diff --git a/demos/pet-demo/src/components/__isograph/iso.ts b/demos/pet-demo/src/components/__isograph/iso.ts index d49365be6..4e32e31d0 100644 --- a/demos/pet-demo/src/components/__isograph/iso.ts +++ b/demos/pet-demo/src/components/__isograph/iso.ts @@ -245,5 +245,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file diff --git a/demos/vite-demo/src/components/__isograph/iso.ts b/demos/vite-demo/src/components/__isograph/iso.ts index 8eac0efd9..4b2850233 100644 --- a/demos/vite-demo/src/components/__isograph/iso.ts +++ b/demos/vite-demo/src/components/__isograph/iso.ts @@ -70,5 +70,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file diff --git a/libs/isograph-compiler/isograph-config-schema.json b/libs/isograph-compiler/isograph-config-schema.json index 8ab82d23b..69599ac44 100644 --- a/libs/isograph-compiler/isograph-config-schema.json +++ b/libs/isograph-compiler/isograph-config-schema.json @@ -80,10 +80,11 @@ "default": false, "type": "boolean" }, - "on_invalid_id_type": { - "$ref": "#/definitions/ConfigFileOptionalValidationLevel" + "no_babel_transform": { + "default": false, + "type": "boolean" }, - "on_missing_babel_transform": { + "on_invalid_id_type": { "$ref": "#/definitions/ConfigFileOptionalValidationLevel" } }, diff --git a/libs/isograph-react/src/tests/__isograph/iso.ts b/libs/isograph-react/src/tests/__isograph/iso.ts index 62eafeb95..2945e3361 100644 --- a/libs/isograph-react/src/tests/__isograph/iso.ts +++ b/libs/isograph-react/src/tests/__isograph/iso.ts @@ -95,5 +95,6 @@ export function iso(_isographLiteralText: string): { throw new Error('iso: Unexpected invocation at runtime. Either the Babel transform ' + 'was not set up, or it failed to identify this call site. Make sure it ' + - 'is being used verbatim as `iso`.'); + 'is being used verbatim as `iso`. If you cannot use the babel transform, ' + + 'set options.no_babel_transform to true in your Isograph config. '); } \ No newline at end of file From 6a60548aec69ebb8911cd186e4fbbe4b2b651078 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Tue, 31 Dec 2024 00:55:00 +0900 Subject: [PATCH 50/61] handle network errors in the quickstart --- docs-website/docs/quickstart.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs-website/docs/quickstart.md b/docs-website/docs/quickstart.md index fc6b8c122..2a1cfd963 100644 --- a/docs-website/docs/quickstart.md +++ b/docs-website/docs/quickstart.md @@ -150,6 +150,11 @@ function makeNetworkRequest( const json = await response.json(); if (response.ok) { + if (json.errors != null) { + throw new Error('GraphQLError', { + cause: json.errors, + }); + } return json; } else { throw new Error('NetworkError', { From 804266dc9fb17ea1567a3a97857dc958ab9de33e Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 1 Jan 2025 15:41:19 +0900 Subject: [PATCH 51/61] fix a few TS errors in compileTag --- libs/isograph-babel-plugin/compileTag.js | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/libs/isograph-babel-plugin/compileTag.js b/libs/isograph-babel-plugin/compileTag.js index 17d5ddb64..4f56f1c6a 100644 --- a/libs/isograph-babel-plugin/compileTag.js +++ b/libs/isograph-babel-plugin/compileTag.js @@ -17,11 +17,15 @@ function compileTag(t, path, config) { // This throws if the tag is invalid compileImportStatement(t, path, type, field, 'entrypoint', config); } else if (keyword === 'field') { - if ( - t.isCallExpression(path.parentPath.node) && - path.parentPath.node.arguments.length === 1 - ) { - path.parentPath.replaceWith(path.parentPath.node.arguments[0]); + if (t.isCallExpression(path.parentPath.node)) { + const firstArg = path.parentPath.node.arguments[0]; + if (path.parentPath.node.arguments.length === 1 && firstArg != null) { + path.parentPath.replaceWith(firstArg); + } else { + throw new Error( + 'Invalid iso tag usage. The iso function should be passed at most one argument.', + ); + } } else { path.replaceWith( t.arrowFunctionExpression([t.identifier('x')], t.identifier('x')), @@ -45,26 +49,28 @@ const typeAndFieldRegex = new RegExp( * @param {babel.NodePath} path * */ function getTypeAndField(path) { - if (path.node.arguments.length !== 1) { + const firstArg = path.node.arguments[0]; + if (path.node.arguments.length !== 1 || firstArg == null) { throw new Error( `BabelPluginIsograph: Iso invocation require one parameter, found ${path.node.arguments.length}`, ); } - if (path.node.arguments[0].type !== 'TemplateLiteral') { + if (firstArg.type !== 'TemplateLiteral') { throw new Error( 'BabelPluginIsograph: Only template literals are allowed in iso fragments.', ); } - const quasis = path.node.arguments[0].quasis; - if (quasis.length !== 1) { + const quasis = firstArg.quasis; + const firstQuasi = quasis[0]; + if (quasis.length !== 1 || firstQuasi == null) { throw new Error( 'BabelPluginIsograph: Substitutions are not allowed in iso fragments.', ); } - const content = quasis[0].value.raw; + const content = firstQuasi.value.raw; const typeAndField = typeAndFieldRegex.exec(content); const keyword = typeAndField?.[1]; From 5c7cc5e98cb2fb209b8fc2f7d77445458a2cdc20 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 1 Jan 2025 15:56:34 +0900 Subject: [PATCH 52/61] fix formatting in Cargo.toml file --- crates/isograph_config/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/isograph_config/Cargo.toml b/crates/isograph_config/Cargo.toml index d0cc9ae17..760c05a3b 100644 --- a/crates/isograph_config/Cargo.toml +++ b/crates/isograph_config/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "isograph_config" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true} +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 87f25cfbf9a343a309c09ff1be309dea9cbd07b9 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 1 Jan 2025 16:15:07 +0900 Subject: [PATCH 53/61] extract build-json-schema job into a separate .yml file. We will reuse this for fixtures --- .github/workflows/ci.yml | 21 +++------------ .../run-cargo-bin-and-ensure-no-changes.yml | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/run-cargo-bin-and-ensure-no-changes.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f4c6edb4..25a71a32f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,25 +72,12 @@ jobs: run: ./artifacts/linux-x64/isograph_cli --config ./demos/${{ matrix.target.folder }}/isograph.config.json - name: 'Check working directory status' run: './scripts/check-git-status.sh' + build-json-schema: name: Build json schema - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install Rust - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - toolchain: stable - override: true - target: x86_64-unknown-linux-musl - - name: Build isograph_config with cargo - run: cargo build --bin isograph_config --target x86_64-unknown-linux-musl --release - - name: Make artifact executable - run: chmod +x ./target/x86_64-unknown-linux-musl/release/isograph_config - - name: Build json schema - run: ./target/x86_64-unknown-linux-musl/release/isograph_config - - name: Check working directory status - run: './scripts/check-git-status.sh' + uses: ./.github/workflows/run-cargo-bin-and-ensure-no-changes.yml + with: + binary: isograph_config typecheck-demos: name: Typecheck and Lint Demos diff --git a/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml b/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml new file mode 100644 index 000000000..fcea14082 --- /dev/null +++ b/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml @@ -0,0 +1,27 @@ +on: + workflow_call: + inputs: + binary: + required: true + type: string + +jobs: + build-and-run-binary: + name: Build and run ${{ inputs.binary }} and ensure no files have been modified + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + override: true + target: x86_64-unknown-linux-musl + - name: Build ${{ inputs.binary }} with cargo + run: cargo build --bin ${{ inputs.binary }} --target x86_64-unknown-linux-musl --release + - name: Make artifact executable + run: chmod +x ./target/x86_64-unknown-linux-musl/release/${{ inputs.binary }} + - name: Run ${{ inputs.binary }} + run: ./target/x86_64-unknown-linux-musl/release/${{ inputs.binary }} + - name: Check working directory status + run: './scripts/check-git-status.sh' From a04be88a6faf69bf89216d6b27d17d1849285557 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 1 Jan 2025 16:18:12 +0900 Subject: [PATCH 54/61] rename isograph_config to build_json_schema --- .github/workflows/ci.yml | 2 +- crates/isograph_config/Cargo.toml | 4 ++++ package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25a71a32f..39fd6da43 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,7 +77,7 @@ jobs: name: Build json schema uses: ./.github/workflows/run-cargo-bin-and-ensure-no-changes.yml with: - binary: isograph_config + binary: build_json_schema typecheck-demos: name: Typecheck and Lint Demos diff --git a/crates/isograph_config/Cargo.toml b/crates/isograph_config/Cargo.toml index 760c05a3b..fd47de820 100644 --- a/crates/isograph_config/Cargo.toml +++ b/crates/isograph_config/Cargo.toml @@ -4,6 +4,10 @@ version = { workspace = true } edition = { workspace = true } license = { workspace = true } +[[bin]] +name = "build_json_schema" +path = "src/main.rs" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/package.json b/package.json index d5ea48c2d..c2ca7f4e9 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "check-rs": "bacon -j check -p ./crates/", "build": "cargo build", "cross": "cross build", - "build-json-schema": "./target/debug/isograph_config", + "build-json-schema": "./target/debug/build_json_schema", "watch-pet-demo": "pnpm --filter=pet-demo iso-watch", "watch-github-demo": "pnpm --filter=github-demo iso-watch", "watch-isograph-react-demo": "pnpm --filter=@isograph/react iso-watch", From f1d9f090dcd6bfced6c51b994e49f80f97c55f86 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 1 Jan 2025 16:20:38 +0900 Subject: [PATCH 55/61] initial scaffolding to generate isograph fixtures --- .github/workflows/ci.yml | 7 +++++++ Cargo.lock | 6 +++++- crates/isograph_fixture_tests/Cargo.toml | 9 +++++++++ crates/isograph_fixture_tests/src/main.rs | 3 +++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 crates/isograph_fixture_tests/Cargo.toml create mode 100644 crates/isograph_fixture_tests/src/main.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39fd6da43..6240b4f08 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -206,11 +206,18 @@ jobs: - name: Run cargo test run: cargo test + build-fixtures: + name: Build fixtures + uses: ./.github/workflows/run-cargo-bin-and-ensure-no-changes.yml + with: + binary: generate_isograph_fixtures + all-checks-passed: name: All checks passed runs-on: ubuntu-latest needs: [ + build-fixtures, build-js-packages, build-cli, build-cli-linux, diff --git a/Cargo.lock b/Cargo.lock index 707b7a377..d0ec5f3e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -785,6 +785,10 @@ dependencies = [ "tracing", ] +[[package]] +name = "isograph_fixture_tests" +version = "0.3.0" + [[package]] name = "isograph_lang_parser" version = "0.3.0" diff --git a/crates/isograph_fixture_tests/Cargo.toml b/crates/isograph_fixture_tests/Cargo.toml new file mode 100644 index 000000000..181935b09 --- /dev/null +++ b/crates/isograph_fixture_tests/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "isograph_fixture_tests" +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } + +[[bin]] +name = "generate_isograph_fixtures" +path = "src/main.rs" diff --git a/crates/isograph_fixture_tests/src/main.rs b/crates/isograph_fixture_tests/src/main.rs new file mode 100644 index 000000000..3993d9595 --- /dev/null +++ b/crates/isograph_fixture_tests/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Generate Isograph fixtures!") +} From bc4e49e0cd862e6a2624f5f094f9d10243b2a8c1 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 1 Jan 2025 16:21:56 +0900 Subject: [PATCH 56/61] rename job to have shorter name, so that in GitHub CI it looks nice --- .github/workflows/run-cargo-bin-and-ensure-no-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml b/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml index fcea14082..2c55a5eca 100644 --- a/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml +++ b/.github/workflows/run-cargo-bin-and-ensure-no-changes.yml @@ -7,7 +7,7 @@ on: jobs: build-and-run-binary: - name: Build and run ${{ inputs.binary }} and ensure no files have been modified + name: Run ${{ inputs.binary }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From 6f46f574588086cd04fae125bcdb05ccd65f739f Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Wed, 1 Jan 2025 16:44:09 +0900 Subject: [PATCH 57/61] format Cargo.toml files --- crates/common_lang_types/Cargo.toml | 2 +- crates/graphql_artifact_generation/Cargo.toml | 4 ++-- crates/graphql_lang_types/Cargo.toml | 6 +++--- crates/graphql_schema_parser/Cargo.toml | 2 +- crates/isograph_cli/Cargo.toml | 2 +- crates/isograph_lang_parser/Cargo.toml | 6 +++--- crates/isograph_lang_types/Cargo.toml | 6 +++--- crates/isograph_lsp/Cargo.toml | 15 +++++++++------ crates/tests/Cargo.toml | 2 +- 9 files changed, 24 insertions(+), 21 deletions(-) diff --git a/crates/common_lang_types/Cargo.toml b/crates/common_lang_types/Cargo.toml index df5b2f5da..f1ed500b5 100644 --- a/crates/common_lang_types/Cargo.toml +++ b/crates/common_lang_types/Cargo.toml @@ -8,4 +8,4 @@ license = { workspace = true } [dependencies] intern = { path = "../../relay-crates/intern" } string_key_newtype = { path = "../string_key_newtype" } -serde = { workspace = true } +serde = { workspace = true } diff --git a/crates/graphql_artifact_generation/Cargo.toml b/crates/graphql_artifact_generation/Cargo.toml index 77c59cf63..a5b4137d8 100644 --- a/crates/graphql_artifact_generation/Cargo.toml +++ b/crates/graphql_artifact_generation/Cargo.toml @@ -7,8 +7,8 @@ license = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -pathdiff = { workspace = true } -lazy_static = { workspace = true } +pathdiff = { workspace = true } +lazy_static = { workspace = true } isograph_schema = { path = "../isograph_schema" } isograph_config = { path = "../isograph_config" } isograph_lang_types = { path = "../isograph_lang_types" } diff --git a/crates/graphql_lang_types/Cargo.toml b/crates/graphql_lang_types/Cargo.toml index 298fc9955..93cc429fd 100644 --- a/crates/graphql_lang_types/Cargo.toml +++ b/crates/graphql_lang_types/Cargo.toml @@ -7,6 +7,6 @@ license = { workspace = true } [dependencies] intern = { path = "../../relay-crates/intern" } common_lang_types = { path = "../common_lang_types" } -strum = { version="0.25.0", features=["derive"] } -serde = { workspace = true } -thiserror = { workspace = true } +strum = { version = "0.25.0", features = ["derive"] } +serde = { workspace = true } +thiserror = { workspace = true } diff --git a/crates/graphql_schema_parser/Cargo.toml b/crates/graphql_schema_parser/Cargo.toml index 6ad464880..7807c3db7 100644 --- a/crates/graphql_schema_parser/Cargo.toml +++ b/crates/graphql_schema_parser/Cargo.toml @@ -9,5 +9,5 @@ graphql-syntax = { path = "../../relay-crates/graphql-syntax" } intern = { path = "../../relay-crates/intern" } graphql_lang_types = { path = "../graphql_lang_types" } common_lang_types = { path = "../common_lang_types" } -logos = { workspace = true } +logos = { workspace = true } thiserror = { workspace = true } diff --git a/crates/isograph_cli/Cargo.toml b/crates/isograph_cli/Cargo.toml index 17a5b9ee5..7406fb702 100644 --- a/crates/isograph_cli/Cargo.toml +++ b/crates/isograph_cli/Cargo.toml @@ -7,7 +7,7 @@ license = { workspace = true } [dependencies] isograph_compiler = { path = "../isograph_compiler" } isograph_config = { path = "../isograph_config" } -isograph_lsp= { path = "../isograph_lsp"} +isograph_lsp = { path = "../isograph_lsp" } colored = { workspace = true } clap = { workspace = true } thiserror = { workspace = true } diff --git a/crates/isograph_lang_parser/Cargo.toml b/crates/isograph_lang_parser/Cargo.toml index 7e1afec96..298fe8f81 100644 --- a/crates/isograph_lang_parser/Cargo.toml +++ b/crates/isograph_lang_parser/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "isograph_lang_parser" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true} +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [dependencies] common_lang_types = { path = "../common_lang_types" } diff --git a/crates/isograph_lang_types/Cargo.toml b/crates/isograph_lang_types/Cargo.toml index ed3829eee..12353721c 100644 --- a/crates/isograph_lang_types/Cargo.toml +++ b/crates/isograph_lang_types/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "isograph_lang_types" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true} +version = { workspace = true } +edition = { workspace = true } +license = { workspace = true } [dependencies] common_lang_types = { path = "../common_lang_types" } diff --git a/crates/isograph_lsp/Cargo.toml b/crates/isograph_lsp/Cargo.toml index 363a49cab..af858f5cb 100644 --- a/crates/isograph_lsp/Cargo.toml +++ b/crates/isograph_lsp/Cargo.toml @@ -5,16 +5,19 @@ edition = { workspace = true } license = { workspace = true } [dependencies] -common_lang_types = { path = "../common_lang_types"} +common_lang_types = { path = "../common_lang_types" } intern = { path = "../../relay-crates/intern" } -isograph_compiler = { path = "../isograph_compiler"} -isograph_lang_parser = { path = "../isograph_lang_parser"} -isograph_lang_types = { path = "../isograph_lang_types"} -isograph_config = { path = "../isograph_config"} +isograph_compiler = { path = "../isograph_compiler" } +isograph_lang_parser = { path = "../isograph_lang_parser" } +isograph_lang_types = { path = "../isograph_lang_types" } +isograph_config = { path = "../isograph_config" } log = { workspace = true, features = ["kv_unstable", "kv_unstable_std"] } lsp-server = { workspace = true } lsp-types = { workspace = true } serde = { workspace = true, features = ["derive", "rc"] } -serde_json = { workspace = true, features = ["float_roundtrip", "unbounded_depth"] } +serde_json = { workspace = true, features = [ + "float_roundtrip", + "unbounded_depth", +] } crossbeam = { workspace = true } tokio = { workspace = true, features = ["full", "test-util", "tracing"] } diff --git a/crates/tests/Cargo.toml b/crates/tests/Cargo.toml index bda57aaf8..77d017abd 100644 --- a/crates/tests/Cargo.toml +++ b/crates/tests/Cargo.toml @@ -19,4 +19,4 @@ colorize = { workspace = true } serde = { workspace = true } [dev-dependencies] -graphql_schema_parser = { path = "../graphql_schema_parser"} +graphql_schema_parser = { path = "../graphql_schema_parser" } From 01e00077a229a636405486487cf061c526ac4a80 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Thu, 2 Jan 2025 18:06:17 +0900 Subject: [PATCH 58/61] ignore DS_Store files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7216a25a0..4ab861af6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ **/.next/** **/next-env.d.ts **/*.d.ts +**/.DS_Store # the built binary is downloaded into this folder during CI artifacts From 490f25c696c24dd37bbaec69276554721589eb17 Mon Sep 17 00:00:00 2001 From: Robert Balicki Date: Fri, 3 Jan 2025 14:27:00 +0900 Subject: [PATCH 59/61] clarify dev workflow doc --- docs-website/docs/development-workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs-website/docs/development-workflow.md b/docs-website/docs/development-workflow.md index 5e3e0a321..4be2fa44e 100644 --- a/docs-website/docs/development-workflow.md +++ b/docs-website/docs/development-workflow.md @@ -31,7 +31,7 @@ These commands will install the appropriate node.js and pnpm version used by Iso ### Rust -Isograph currently uses `rustc 1.81.0 (eeb90cda1 2024-09-04)`. Rust is fairly stable and we don't rely on anything crazy, so it should be safe to keep your `rustc` up-to-date. +Isograph is built (in CI) using the latest stable version. Rust is fairly stable and we don't rely on anything crazy, so it should be safe to keep your `rustc` up-to-date. You should also install [`bacon`](https://dystroy.org/bacon/) via `cargo install --locked bacon`. From 469f8f02ebfb6d16eedda889112613d7c92b334c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:07:00 +0100 Subject: [PATCH 60/61] fix cleanup --- .../src/useLazyDisposableState.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts index 930752c36..ba2e52297 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts @@ -5,8 +5,8 @@ import { useEffect, useRef } from 'react'; import type { ItemCleanupPair } from '@isograph/isograph-disposable-types'; import { ParentCache } from './ParentCache'; -import { type UnassignedState } from './useUpdatableDisposableState'; import { useCachedResponsivePrecommitValue } from './useCachedResponsivePrecommitValue'; +import { type UnassignedState } from './useUpdatableDisposableState'; /** * useLazyDisposableState @@ -42,7 +42,8 @@ export function useLazyDisposableState( lastCommittedParentCache.current = parentCache; return () => { - const cleanupFn = itemCleanupPairRef.current?.[1]; + // current is a stale variable + const cleanupFn = current?.[1]; // TODO confirm useEffect is called in order. if (cleanupFn == null) { throw new Error( From a6077e1d871c12a5e5f3f6409313e5884134b192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patryk=20Wa=C5=82ach?= <35966385+PatrykWalach@users.noreply.github.com> Date: Thu, 14 Nov 2024 15:26:30 +0100 Subject: [PATCH 61/61] don't use stale itemCleanupPairRef --- .../src/useLazyDisposableState.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts index ba2e52297..889be211f 100644 --- a/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts +++ b/libs/isograph-react-disposable-state/src/useLazyDisposableState.ts @@ -42,8 +42,7 @@ export function useLazyDisposableState( lastCommittedParentCache.current = parentCache; return () => { - // current is a stale variable - const cleanupFn = current?.[1]; + const cleanupFn = itemCleanupPairRef.current?.[1]; // TODO confirm useEffect is called in order. if (cleanupFn == null) { throw new Error(