From 092237ebf8686f92f44a791a194c03c7aa090dd9 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 17:26:41 +0900 Subject: [PATCH 01/17] Remove getOperation function --- .../__tests__/componentRegistry.test.tsx | 6 +- packages/fabrix/__tests__/query.test.tsx | 30 +-- packages/fabrix/src/fetcher.ts | 4 +- packages/fabrix/src/renderer.tsx | 205 +++++++++--------- 4 files changed, 110 insertions(+), 135 deletions(-) diff --git a/packages/fabrix/__tests__/componentRegistry.test.tsx b/packages/fabrix/__tests__/componentRegistry.test.tsx index 13c653f8..9ac716c4 100644 --- a/packages/fabrix/__tests__/componentRegistry.test.tsx +++ b/packages/fabrix/__tests__/componentRegistry.test.tsx @@ -136,7 +136,7 @@ describe("getFabrixComponent", () => { title: "Custom Table", }} query={gql` - query getUsers { + query { users { collection { id @@ -149,9 +149,7 @@ describe("getFabrixComponent", () => { > {({ getComponent }) => { return ( -
- {getComponent("getUsers", "users")} -
+
{getComponent("users")}
); }} , diff --git a/packages/fabrix/__tests__/query.test.tsx b/packages/fabrix/__tests__/query.test.tsx index 56af6d54..6f047946 100644 --- a/packages/fabrix/__tests__/query.test.tsx +++ b/packages/fabrix/__tests__/query.test.tsx @@ -84,21 +84,10 @@ describe("collection", () => { const testPatterns = [ ["collection", collectionQuery, undefined], ["edges", edgeQuery, undefined], - [ - "getOperation", - collectionQuery, - ({ getOperation }) => getOperation("getUsers"), - ], - [ - "getOperation/getComponent", - collectionQuery, - ({ getOperation }) => - getOperation("getUsers", ({ getComponent }) => getComponent("users")), - ], [ "getComponent", collectionQuery, - ({ getComponent }) => getComponent("getUsers", "users"), + ({ getComponent }) => getComponent("users"), ], ] satisfies [string, string, FabrixComponentChildrenProps][]; @@ -233,21 +222,4 @@ describe("collection", () => { { components }, ); }); - - it("should be able to access the response data for by an operation", async () => { - await testWithUnmount( - - {({ getOperation }) => - getOperation<{ users: { size: number } }>("getUsers", ({ data }) => ( -
{data.users.size}
- )) - } -
, - async () => { - const result = await screen.findByRole("result-size"); - expect(result).toBeInTheDocument(); - expect(result).toHaveTextContent(users.length.toString()); - }, - ); - }); }); diff --git a/packages/fabrix/src/fetcher.ts b/packages/fabrix/src/fetcher.ts index 88a4ddce..0c824750 100644 --- a/packages/fabrix/src/fetcher.ts +++ b/packages/fabrix/src/fetcher.ts @@ -4,10 +4,10 @@ import { useClient, useQuery } from "urql"; export const useDataFetch = (props: { query: DocumentNode | string; variables?: Record; + pause?: boolean; }) => { const [{ data, fetching, error }] = useQuery({ - query: props.query, - variables: props.variables, + ...props, }); return { diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 663ac70f..0eadb5fe 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -1,5 +1,13 @@ +<<<<<<< HEAD import { DirectiveNode, DocumentNode, OperationTypeNode, parse } from "graphql"; import { ReactNode, useCallback, useContext, useMemo } from "react"; +||||||| parent of 8ca3de2 (Remove getOperation function) +import { DirectiveNode, DocumentNode, OperationTypeNode } from "graphql"; +import { ReactNode, useCallback, useContext, useMemo } from "react"; +======= +import { DirectiveNode, DocumentNode, OperationTypeNode } from "graphql"; +import { ReactNode, useContext, useMemo } from "react"; +>>>>>>> 8ca3de2 (Remove getOperation function) import { findDirective, parseDirectiveArguments } from "@directive"; import { ViewRenderer } from "@renderers/fields"; import { FormRenderer } from "@renderers/form"; @@ -185,6 +193,7 @@ export type FabrixComponentProps = FabrixComponentCommonProps & { type FabrixComponentChildrenExtraProps = { key?: string; className?: string }; +<<<<<<< HEAD type FabrixGetComponentFn = ( /** * The name that corresponds to the GQL query. @@ -205,23 +214,41 @@ export type FabrixGetOperationFn = < ) => ReactNode; export type FabrixComponentChildrenProps = { +||||||| parent of 8ca3de2 (Remove getOperation function) +type FabrixGetComponentFn = ( /** - * Get the operation result by operation name or index - * - * ```tsx - * - * {({ getOperation }) => ( - * {getOperation("getUsers", ({ data, getComponent }) => ( - * <> - *

{data.users.size} users

- * {getComponent("users")} - * - * ))} - * )} - *
- * ``` + * The name that corresponds to the GQL query. */ + name: string, + extraProps?: FabrixComponentChildrenExtraProps, + fieldsRenderer?: FabrixComponentFieldsRenderer, +) => ReactNode; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type FabrixGetOperationFn = ( + indexOrName: number | string, + renderer?: (props: { + data: TData; + getComponent: FabrixGetComponentFn; + }) => ReactNode, +) => ReactNode; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type FabrixComponentChildrenProps = { +======= +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type FabrixComponentChildrenProps = { +>>>>>>> 8ca3de2 (Remove getOperation function) + /** + * The data fetched from the query + */ +<<<<<<< HEAD getOperation: FabrixGetOperationFn; +||||||| parent of 8ca3de2 (Remove getOperation function) + getOperation: FabrixGetOperationFn; +======= + data: TData; +>>>>>>> 8ca3de2 (Remove getOperation function) /** * Get the component by root field name @@ -229,13 +256,12 @@ export type FabrixComponentChildrenProps = { * ```tsx * * {({ getComponent }) => ( - * {getComponent("getUsers", "users")} + * {getComponent("users")} * )} * * ``` */ getComponent: ( - operationIndexOrName: number | string, rootFieldName: string, extraProps?: FabrixComponentChildrenExtraProps, fieldsRenderer?: FabrixComponentFieldsRenderer, @@ -302,11 +328,16 @@ export const FabrixComponent = (props: FabrixComponentProps) => { return
{renderComponent()}
; }; -export const getComponentRendererFn = ( - props: FabrixComponentProps, +export const getComponentRendererFn = < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, +>( + props: FabrixComponentProps, getComponent: ReturnType, ) => { + const context = useContext(FabrixContext); const { fieldConfigs } = useFieldConfigs(props.query); +<<<<<<< HEAD const getOperation: FabrixComponentChildrenProps["getOperation"] = useCallback( (indexOrName, renderer) => { @@ -330,24 +361,65 @@ export const getComponentRendererFn = ( }, [fieldConfigs, props.variables], ); +||||||| parent of 8ca3de2 (Remove getOperation function) + const getOperation: FabrixComponentChildrenProps["getOperation"] = + useCallback( + (indexOrName, renderer) => { + const fieldConfig = + typeof indexOrName === "number" + ? fieldConfigs[indexOrName] + : fieldConfigs.find(({ name }) => name == indexOrName); + if (!fieldConfig) { + throw new Error(`No operation found for indexOrName: ${indexOrName}`); + } + + return ( + + ); + }, + [fieldConfigs, props.variables], + ); +======= + const fieldConfig = fieldConfigs[0]; + if (!fieldConfig) { + throw new Error(`No operation found`); + } +>>>>>>> 8ca3de2 (Remove getOperation function) return () => { + const { fetching, error, data } = useDataFetch({ + query: fieldConfig.document, + variables: props.variables, + pause: fieldConfig.type !== OperationTypeNode.QUERY, + }); + + if (fetching) { + return ; + } + + if (error) { + throw error; + } + + const component = getComponent(fieldConfig, data, context); if (props.children) { return props.children({ - getOperation, - getComponent: ( - operationIndexOrName, - rootFieldName, - extraProps, - fieldsRenderer, - ) => - getOperation(operationIndexOrName, ({ getComponent }) => - getComponent(rootFieldName, extraProps, fieldsRenderer), - ), + data: data as TData, + getComponent: component, }); } - return fieldConfigs.map((_, i) => getOperation(i)); + return fieldConfig.fields.map((field) => + component(field.name, { + key: `fabrix-${fieldConfig.name}-${field.name}`, + }), + ); }; }; @@ -362,7 +434,7 @@ export const getComponentFn = (props: FabrixComponentProps, rendererFn: RendererFn) => ( fieldConfig: FieldConfigs, - data: FabrixComponentData, + data: FabrixComponentData | undefined, context: FabrixContextType, ) => ( @@ -375,81 +447,14 @@ export const getComponentFn = throw new Error(`No root field found for name: ${name}`); } + const dataByName = data ? (name in data ? data[name] : {}) : {}; + return (
- {rendererFn(field, data[name], context, componentFieldsRenderer)} + {rendererFn(field, dataByName, context, componentFieldsRenderer)}
); }; - -type GetComponentFn = ( - op: FieldConfigs, - data: FabrixComponentData, - context: FabrixContextType, -) => FabrixGetComponentFn; - -type RendererCommonProps = { - key: string; - operation: FieldConfigs; - variables: Record | undefined; - renderer?: Parameters[1]; - getComponentFn: GetComponentFn; - extraClassName?: string; -}; - -const OperationRenderer = (props: RendererCommonProps) => { - return props.operation.type === OperationTypeNode.MUTATION ? ( - - ) : ( - - ); -}; - -const QueryOperationRenderer = ({ - operation, - variables, - renderer, - getComponentFn, -}: RendererCommonProps) => { - const context = useContext(FabrixContext); - const { fetching, error, data } = useDataFetch({ - query: operation.document, - variables, - }); - - if (fetching || !data) { - return ; - } - - if (error) { - throw error; - } - - const getComponent = getComponentFn(operation, data, context); - return renderer - ? renderer({ data, getComponent }) - : operation.fields.map((field) => - getComponent(field.name, { - key: `fabrix-query-${operation.name}-${field.name}`, - }), - ); -}; - -const MutateOperationRenderer = ({ - operation, - renderer, - getComponentFn, -}: RendererCommonProps) => { - const context = useContext(FabrixContext); - const getComponent = getComponentFn(operation, {}, context); - return renderer - ? renderer({ data: {}, getComponent }) - : operation.fields.map((field) => - getComponent(field.name, { - key: `fabrix-mutation-${operation.name}-${field.name}`, - }), - ); -}; From 23beb28ea184551ec00bd7af0b2d9747c8fa8011 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 17:45:13 +0900 Subject: [PATCH 02/17] Fix conflict --- packages/fabrix/__tests__/query.test.tsx | 24 +++++ packages/fabrix/src/renderer.tsx | 110 +---------------------- 2 files changed, 25 insertions(+), 109 deletions(-) diff --git a/packages/fabrix/__tests__/query.test.tsx b/packages/fabrix/__tests__/query.test.tsx index 6f047946..9e155359 100644 --- a/packages/fabrix/__tests__/query.test.tsx +++ b/packages/fabrix/__tests__/query.test.tsx @@ -222,4 +222,28 @@ describe("collection", () => { { components }, ); }); + + it("should be able to access the response data for by an operation", async () => { + await testWithUnmount( + + {({ data }) => ( + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access +
{data.users.size}
+ )} +
, + async () => { + const result = await screen.findByRole("result-size"); + expect(result).toBeInTheDocument(); + expect(result).toHaveTextContent(users.length.toString()); + }, + ); + }); }); diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 0eadb5fe..85267d8e 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -1,13 +1,5 @@ -<<<<<<< HEAD import { DirectiveNode, DocumentNode, OperationTypeNode, parse } from "graphql"; -import { ReactNode, useCallback, useContext, useMemo } from "react"; -||||||| parent of 8ca3de2 (Remove getOperation function) -import { DirectiveNode, DocumentNode, OperationTypeNode } from "graphql"; -import { ReactNode, useCallback, useContext, useMemo } from "react"; -======= -import { DirectiveNode, DocumentNode, OperationTypeNode } from "graphql"; import { ReactNode, useContext, useMemo } from "react"; ->>>>>>> 8ca3de2 (Remove getOperation function) import { findDirective, parseDirectiveArguments } from "@directive"; import { ViewRenderer } from "@renderers/fields"; import { FormRenderer } from "@renderers/form"; @@ -193,62 +185,12 @@ export type FabrixComponentProps = FabrixComponentCommonProps & { type FabrixComponentChildrenExtraProps = { key?: string; className?: string }; -<<<<<<< HEAD -type FabrixGetComponentFn = ( - /** - * The name that corresponds to the GQL query. - */ - name: string, - extraProps?: FabrixComponentChildrenExtraProps, - fieldsRenderer?: FabrixComponentFieldsRenderer, -) => ReactNode; - -export type FabrixGetOperationFn = < - T extends Record = Record, ->( - indexOrName: number | string, - renderer?: (props: { - data: T; - getComponent: FabrixGetComponentFn; - }) => ReactNode, -) => ReactNode; - -export type FabrixComponentChildrenProps = { -||||||| parent of 8ca3de2 (Remove getOperation function) -type FabrixGetComponentFn = ( - /** - * The name that corresponds to the GQL query. - */ - name: string, - extraProps?: FabrixComponentChildrenExtraProps, - fieldsRenderer?: FabrixComponentFieldsRenderer, -) => ReactNode; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type FabrixGetOperationFn = ( - indexOrName: number | string, - renderer?: (props: { - data: TData; - getComponent: FabrixGetComponentFn; - }) => ReactNode, -) => ReactNode; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type FabrixComponentChildrenProps = { -======= // eslint-disable-next-line @typescript-eslint/no-explicit-any export type FabrixComponentChildrenProps = { ->>>>>>> 8ca3de2 (Remove getOperation function) /** * The data fetched from the query */ -<<<<<<< HEAD - getOperation: FabrixGetOperationFn; -||||||| parent of 8ca3de2 (Remove getOperation function) - getOperation: FabrixGetOperationFn; -======= data: TData; ->>>>>>> 8ca3de2 (Remove getOperation function) /** * Get the component by root field name @@ -332,65 +274,15 @@ export const getComponentRendererFn = < // eslint-disable-next-line @typescript-eslint/no-explicit-any TData = any, >( - props: FabrixComponentProps, + props: FabrixComponentProps, getComponent: ReturnType, ) => { const context = useContext(FabrixContext); const { fieldConfigs } = useFieldConfigs(props.query); -<<<<<<< HEAD - const getOperation: FabrixComponentChildrenProps["getOperation"] = - useCallback( - (indexOrName, renderer) => { - const fieldConfig = - typeof indexOrName === "number" - ? fieldConfigs[indexOrName] - : fieldConfigs.find(({ name }) => name == indexOrName); - if (!fieldConfig) { - throw new Error(`No operation found for indexOrName: ${indexOrName}`); - } - - return ( - [1]} - /> - ); - }, - [fieldConfigs, props.variables], - ); -||||||| parent of 8ca3de2 (Remove getOperation function) - const getOperation: FabrixComponentChildrenProps["getOperation"] = - useCallback( - (indexOrName, renderer) => { - const fieldConfig = - typeof indexOrName === "number" - ? fieldConfigs[indexOrName] - : fieldConfigs.find(({ name }) => name == indexOrName); - if (!fieldConfig) { - throw new Error(`No operation found for indexOrName: ${indexOrName}`); - } - - return ( - - ); - }, - [fieldConfigs, props.variables], - ); -======= const fieldConfig = fieldConfigs[0]; if (!fieldConfig) { throw new Error(`No operation found`); } ->>>>>>> 8ca3de2 (Remove getOperation function) return () => { const { fetching, error, data } = useDataFetch({ From c047f0f76272defe8259aadaacaa842fb8372fe3 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 17:29:18 +0900 Subject: [PATCH 03/17] Fix exmaple app --- examples/vite-todoapp/src/App.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/vite-todoapp/src/App.tsx b/examples/vite-todoapp/src/App.tsx index e332036f..b2059229 100644 --- a/examples/vite-todoapp/src/App.tsx +++ b/examples/vite-todoapp/src/App.tsx @@ -32,7 +32,11 @@ function App() { id } } - + `} + /> + Date: Mon, 20 Jan 2025 17:47:21 +0900 Subject: [PATCH 04/17] Add a changeset --- .changeset/quick-teachers-melt.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/quick-teachers-melt.md diff --git a/.changeset/quick-teachers-melt.md b/.changeset/quick-teachers-melt.md new file mode 100644 index 00000000..0d1cd4a1 --- /dev/null +++ b/.changeset/quick-teachers-melt.md @@ -0,0 +1,7 @@ +--- +"@fabrix-framework/fabrix": minor +--- + +Remove `getOperation` function from children props in FabrixComponent. + +Now `query` prop in `FabrixComponent` supports a single query to get it future compatible with TypedDocumentNode. From 533f1055072760b9074d61a906816cbc2d6832a1 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 17:49:42 +0900 Subject: [PATCH 05/17] Remove TData --- packages/fabrix/src/renderer.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 85267d8e..425dab00 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -185,12 +185,12 @@ export type FabrixComponentProps = FabrixComponentCommonProps & { type FabrixComponentChildrenExtraProps = { key?: string; className?: string }; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type FabrixComponentChildrenProps = { +export type FabrixComponentChildrenProps = { /** * The data fetched from the query */ - data: TData; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data: any; /** * Get the component by root field name @@ -270,10 +270,7 @@ export const FabrixComponent = (props: FabrixComponentProps) => { return
{renderComponent()}
; }; -export const getComponentRendererFn = < - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TData = any, ->( +export const getComponentRendererFn = ( props: FabrixComponentProps, getComponent: ReturnType, ) => { @@ -302,7 +299,7 @@ export const getComponentRendererFn = < const component = getComponent(fieldConfig, data, context); if (props.children) { return props.children({ - data: data as TData, + data, getComponent: component, }); } From ba327b6fb658d88b1019ab17fd6283108977ca51 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 17:51:19 +0900 Subject: [PATCH 06/17] Modified changeset --- .changeset/quick-teachers-melt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/quick-teachers-melt.md b/.changeset/quick-teachers-melt.md index 0d1cd4a1..6f1f0f83 100644 --- a/.changeset/quick-teachers-melt.md +++ b/.changeset/quick-teachers-melt.md @@ -4,4 +4,4 @@ Remove `getOperation` function from children props in FabrixComponent. -Now `query` prop in `FabrixComponent` supports a single query to get it future compatible with TypedDocumentNode. +Now `query` prop in `FabrixComponent` supports only a single query to get it future compatible with TypedDocumentNode. From 5ed40a75059caef2d14ca31392c011e5a07641c4 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 18:06:36 +0900 Subject: [PATCH 07/17] TypedDocumentNode support --- packages/fabrix/package.json | 1 + packages/fabrix/src/renderer.tsx | 45 +++++++++++++++++++++++--------- packages/fabrix/src/visitor.ts | 22 +++++++++++++--- pnpm-lock.yaml | 14 +++++++--- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/packages/fabrix/package.json b/packages/fabrix/package.json index 3e6e8e72..dca937cf 100644 --- a/packages/fabrix/package.json +++ b/packages/fabrix/package.json @@ -58,6 +58,7 @@ "@fabrix-framework/eslint-config": "workspace:*", "@fabrix-framework/prettier-config": "workspace:*", "@faker-js/faker": "^9.4.0", + "@graphql-typed-document-node/core": "^3.2.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^14.5.2", diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 425dab00..32b95388 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -1,4 +1,4 @@ -import { DirectiveNode, DocumentNode, OperationTypeNode, parse } from "graphql"; +import { DirectiveNode, DocumentNode, OperationTypeNode } from "graphql"; import { ReactNode, useContext, useMemo } from "react"; import { findDirective, parseDirectiveArguments } from "@directive"; import { ViewRenderer } from "@renderers/fields"; @@ -9,7 +9,11 @@ import { directiveSchemaMap } from "@directive/schema"; import { mergeFieldConfigs } from "@readers/shared"; import { buildDefaultViewFieldConfigs, viewFieldMerger } from "@readers/field"; import { buildDefaultFormFieldConfigs, formFieldMerger } from "@readers/form"; -import { buildRootDocument, FieldVariables } from "@/visitor"; +import { + buildRootDocument, + FieldVariables, + GeneralDocumentType, +} from "@/visitor"; import { Field, Fields } from "@/visitor/fields"; import { FabrixComponentData, useDataFetch, Value } from "@/fetcher"; @@ -105,10 +109,15 @@ export type FieldConfigs = { fields: FieldConfig[]; }; -export const useFieldConfigs = (query: DocumentNode | string) => { - const rootDocument = buildRootDocument( - typeof query === "string" ? parse(query) : query, - ); +export const useFieldConfigs = < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TVariables = Record, +>( + query: GeneralDocumentType, +) => { + const rootDocument = buildRootDocument(query); const context = useContext(FabrixContext); const fieldConfigs = useMemo(() => { return rootDocument.map(({ name, document, fields, opType, variables }) => @@ -142,11 +151,14 @@ export const useFieldConfigs = (query: DocumentNode | string) => { return { fieldConfigs }; }; -type FabrixComponentCommonProps = { +type FabrixComponentCommonProps< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TVariables = Record, +> = { /** * The variables to call the query with. */ - variables?: Record; + variables?: TVariables; /** * The title of the query. @@ -164,7 +176,12 @@ type FabrixComponentCommonProps = { contentClassName?: string; }; -export type FabrixComponentProps = FabrixComponentCommonProps & { +export type FabrixComponentProps< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TVariables = Record, +> = FabrixComponentCommonProps & { /** * The query to render. * @@ -178,19 +195,21 @@ export type FabrixComponentProps = FabrixComponentCommonProps & { * } * ``` */ - query: DocumentNode | string; + query: GeneralDocumentType; children?: (props: FabrixComponentChildrenProps) => ReactNode; }; type FabrixComponentChildrenExtraProps = { key?: string; className?: string }; -export type FabrixComponentChildrenProps = { +export type FabrixComponentChildrenProps< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, +> = { /** * The data fetched from the query */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data: any; + data: TData; /** * Get the component by root field name diff --git a/packages/fabrix/src/visitor.ts b/packages/fabrix/src/visitor.ts index 15908b9a..ac1d0ec6 100644 --- a/packages/fabrix/src/visitor.ts +++ b/packages/fabrix/src/visitor.ts @@ -1,4 +1,5 @@ import { Fields, SelectionField } from "@visitor/fields"; +import { TypedDocumentNode } from "@graphql-typed-document-node/core"; import { DirectiveNode, DocumentNode, @@ -26,15 +27,28 @@ type S = { fields: Fields; }; -export const buildRootDocument = (document: DocumentNode) => - document.definitions.map((def) => +export type GeneralDocumentType< + TData = unknown, + TVariables = Record, +> = string | DocumentNode | TypedDocumentNode; + +export const buildRootDocument = < + TData = unknown, + TVariables = Record, +>( + document: GeneralDocumentType, +) => { + const parsedDocument = + typeof document === "string" ? parse(document) : document; + return parsedDocument.definitions.map((def) => buildQueryStructure({ kind: Kind.DOCUMENT, definitions: [def], }), ); +}; -const buildQueryStructure = (ast: DocumentNode | string) => { +const buildQueryStructure = (ast: DocumentNode) => { const operationStructure = {} as S; const extractTypeNode = (node: TypeNode) => { @@ -51,7 +65,7 @@ const buildQueryStructure = (ast: DocumentNode | string) => { }; const currentPath: string[] = []; - visit(typeof ast === "string" ? parse(ast) : ast, { + visit(ast, { OperationDefinition: (node) => { // No fragment support currently if ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42cb8856..cc79d3c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -237,6 +237,9 @@ importers: '@faker-js/faker': specifier: ^9.4.0 version: 9.4.0 + '@graphql-typed-document-node/core': + specifier: ^3.2.0 + version: 3.2.0(graphql@16.10.0) '@testing-library/jest-dom': specifier: ^6.6.3 version: 6.6.3 @@ -336,7 +339,7 @@ importers: version: 9.1.0(eslint@9.18.0) eslint-plugin-import: specifier: ^2.29.1 - version: 2.31.0(eslint@9.18.0) + version: 2.31.0(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0) typescript: specifier: ^5 version: 5.7.3 @@ -7225,16 +7228,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(eslint-import-resolver-node@0.3.9)(eslint@9.18.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.18.0): dependencies: debug: 3.2.7 optionalDependencies: + '@typescript-eslint/parser': 8.19.1(eslint@9.18.0)(typescript@5.7.3) eslint: 9.18.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(eslint@9.18.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@5.7.3))(eslint@9.18.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -7245,7 +7249,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.18.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(eslint-import-resolver-node@0.3.9)(eslint@9.18.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.19.1(eslint@9.18.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint@9.18.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -7256,6 +7260,8 @@ snapshots: semver: 6.3.1 string.prototype.trimend: 1.0.8 tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.19.1(eslint@9.18.0)(typescript@5.7.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack From 6589e466122a1f716dbdbab54f50e44e7c0362e0 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 18:09:02 +0900 Subject: [PATCH 08/17] Add a changeset --- .changeset/orange-peaches-doubt.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/orange-peaches-doubt.md diff --git a/.changeset/orange-peaches-doubt.md b/.changeset/orange-peaches-doubt.md new file mode 100644 index 00000000..bbc08cfb --- /dev/null +++ b/.changeset/orange-peaches-doubt.md @@ -0,0 +1,5 @@ +--- +"@fabrix-framework/fabrix": minor +--- + +Add `TypedDocumentNode` support: now if the `TypedDocumentNode` query is given to `FabrixComponent`, some functions and variables that are accessible thorugh children props are typed. From 2f635cfef059ee1686d5a027bc3d4ae9757829c1 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 18:17:09 +0900 Subject: [PATCH 09/17] Add type variables to FabrixComponent --- packages/fabrix/src/fetcher.ts | 7 +++++-- packages/fabrix/src/renderer.tsx | 26 +++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/fabrix/src/fetcher.ts b/packages/fabrix/src/fetcher.ts index 0c824750..6b7fc507 100644 --- a/packages/fabrix/src/fetcher.ts +++ b/packages/fabrix/src/fetcher.ts @@ -1,12 +1,15 @@ import { DocumentNode } from "graphql"; import { useClient, useQuery } from "urql"; -export const useDataFetch = (props: { +export const useDataFetch = < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, +>(props: { query: DocumentNode | string; variables?: Record; pause?: boolean; }) => { - const [{ data, fetching, error }] = useQuery({ + const [{ data, fetching, error }] = useQuery({ ...props, }); diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 32b95388..89bd807c 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -197,7 +197,7 @@ export type FabrixComponentProps< */ query: GeneralDocumentType; - children?: (props: FabrixComponentChildrenProps) => ReactNode; + children?: (props: FabrixComponentChildrenProps) => ReactNode; }; type FabrixComponentChildrenExtraProps = { key?: string; className?: string }; @@ -251,7 +251,14 @@ export type FabrixComponentChildrenProps< *
* ``` */ -export const FabrixComponent = (props: FabrixComponentProps) => { +export const FabrixComponent = < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TVariables = Record, +>( + props: FabrixComponentProps, +) => { const renderComponent = getComponentRendererFn( props, getComponentFn( @@ -289,8 +296,13 @@ export const FabrixComponent = (props: FabrixComponentProps) => { return
{renderComponent()}
; }; -export const getComponentRendererFn = ( - props: FabrixComponentProps, +export const getComponentRendererFn = < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TVariables = Record, +>( + props: FabrixComponentProps, getComponent: ReturnType, ) => { const context = useContext(FabrixContext); @@ -301,7 +313,7 @@ export const getComponentRendererFn = ( } return () => { - const { fetching, error, data } = useDataFetch({ + const { fetching, error, data } = useDataFetch({ query: fieldConfig.document, variables: props.variables, pause: fieldConfig.type !== OperationTypeNode.QUERY, @@ -315,10 +327,10 @@ export const getComponentRendererFn = ( throw error; } - const component = getComponent(fieldConfig, data, context); + const component = getComponent(fieldConfig, data ?? {}, context); if (props.children) { return props.children({ - data, + data: data ?? ({} as TData), getComponent: component, }); } From 28a19ebdd4ac47ca9d0322c0da8b9b691a67cd83 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 18:18:52 +0900 Subject: [PATCH 10/17] Type rootFieldName argument --- packages/fabrix/src/renderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 89bd807c..dab0dda2 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -223,7 +223,7 @@ export type FabrixComponentChildrenProps< * ``` */ getComponent: ( - rootFieldName: string, + rootFieldName: TData extends Record ? keyof TData : never, extraProps?: FabrixComponentChildrenExtraProps, fieldsRenderer?: FabrixComponentFieldsRenderer, ) => ReactNode; From 39e9bb205c128b33754bec0a867e03051f6bba17 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 18:46:06 +0900 Subject: [PATCH 11/17] Fix type error --- packages/fabrix/src/renderer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index dab0dda2..2296417e 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -223,7 +223,9 @@ export type FabrixComponentChildrenProps< * ``` */ getComponent: ( - rootFieldName: TData extends Record ? keyof TData : never, + rootFieldName: TData extends Record + ? Extract + : never, extraProps?: FabrixComponentChildrenExtraProps, fieldsRenderer?: FabrixComponentFieldsRenderer, ) => ReactNode; From 262f04a781773b51694b49bcffb31b1bfebcfae2 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 18:47:43 +0900 Subject: [PATCH 12/17] Relax type limitation --- packages/fabrix/src/renderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 2296417e..5ae76154 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -225,7 +225,7 @@ export type FabrixComponentChildrenProps< getComponent: ( rootFieldName: TData extends Record ? Extract - : never, + : string, extraProps?: FabrixComponentChildrenExtraProps, fieldsRenderer?: FabrixComponentFieldsRenderer, ) => ReactNode; From 78be4cd2248b942d1c18dc328a188ff3823f7a2e Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 18:52:32 +0900 Subject: [PATCH 13/17] Ignore __typename --- packages/fabrix/src/renderer.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 5ae76154..4dbae930 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -224,7 +224,9 @@ export type FabrixComponentChildrenProps< */ getComponent: ( rootFieldName: TData extends Record - ? Extract + ? Extract extends "__typename" + ? never + : Extract : string, extraProps?: FabrixComponentChildrenExtraProps, fieldsRenderer?: FabrixComponentFieldsRenderer, From fe836d37a8b27f28a5ad8e7b420a96b4253adf5c Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 20:44:59 +0900 Subject: [PATCH 14/17] Fix __typename ignorance again --- packages/fabrix/src/renderer.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 4dbae930..b7ff46f6 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -224,9 +224,7 @@ export type FabrixComponentChildrenProps< */ getComponent: ( rootFieldName: TData extends Record - ? Extract extends "__typename" - ? never - : Extract + ? Exclude, "__typename"> : string, extraProps?: FabrixComponentChildrenExtraProps, fieldsRenderer?: FabrixComponentFieldsRenderer, From a530759b776b302392fe81baecf8ac59b8445761 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 20:58:33 +0900 Subject: [PATCH 15/17] Fix typing on variables --- packages/fabrix/src/renderer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index b7ff46f6..79e70296 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -181,7 +181,7 @@ export type FabrixComponentProps< TData = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any TVariables = Record, -> = FabrixComponentCommonProps & { +> = FabrixComponentCommonProps & { /** * The query to render. * From b857a07fac667c2debc5e8ce4adbd93b00a12ec9 Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 21:05:31 +0900 Subject: [PATCH 16/17] Fix type errors --- packages/fabrix/src/fetcher.ts | 10 +++++++--- packages/fabrix/src/renderer.tsx | 12 ++++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/fabrix/src/fetcher.ts b/packages/fabrix/src/fetcher.ts index 6b7fc507..23754de5 100644 --- a/packages/fabrix/src/fetcher.ts +++ b/packages/fabrix/src/fetcher.ts @@ -1,16 +1,20 @@ import { DocumentNode } from "graphql"; -import { useClient, useQuery } from "urql"; +import { AnyVariables, useClient, useQuery } from "urql"; export const useDataFetch = < // eslint-disable-next-line @typescript-eslint/no-explicit-any TData = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TVariables = Record, >(props: { query: DocumentNode | string; - variables?: Record; + variables: TVariables | undefined; pause?: boolean; }) => { const [{ data, fetching, error }] = useQuery({ - ...props, + query: props.query, + variables: props.variables as AnyVariables, + pause: props.pause, }); return { diff --git a/packages/fabrix/src/renderer.tsx b/packages/fabrix/src/renderer.tsx index 79e70296..4e133a95 100644 --- a/packages/fabrix/src/renderer.tsx +++ b/packages/fabrix/src/renderer.tsx @@ -315,7 +315,7 @@ export const getComponentRendererFn = < } return () => { - const { fetching, error, data } = useDataFetch({ + const { fetching, error, data } = useDataFetch({ query: fieldConfig.document, variables: props.variables, pause: fieldConfig.type !== OperationTypeNode.QUERY, @@ -353,7 +353,15 @@ type RendererFn = ( ) => ReactNode; export const getComponentFn = - (props: FabrixComponentProps, rendererFn: RendererFn) => + < + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TData = any, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TVariables = Record, + >( + props: FabrixComponentProps, + rendererFn: RendererFn, + ) => ( fieldConfig: FieldConfigs, data: FabrixComponentData | undefined, From 3b87bac669fa852c997a64e4b802f911a40cb07e Mon Sep 17 00:00:00 2001 From: IzumiSy Date: Mon, 20 Jan 2025 21:32:49 +0900 Subject: [PATCH 17/17] Modified changeset --- .changeset/orange-peaches-doubt.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/orange-peaches-doubt.md b/.changeset/orange-peaches-doubt.md index bbc08cfb..8bbbef9a 100644 --- a/.changeset/orange-peaches-doubt.md +++ b/.changeset/orange-peaches-doubt.md @@ -2,4 +2,6 @@ "@fabrix-framework/fabrix": minor --- -Add `TypedDocumentNode` support: now if the `TypedDocumentNode` query is given to `FabrixComponent`, some functions and variables that are accessible thorugh children props are typed. +Add `TypedDocumentNode` support: now when the `TypedDocumentNode` query is given, `data` and `variables` are typed. + +Also, `getComponent` has the first argument that is typed to help users select the component associated to the query.