Skip to content

Commit 7c104b0

Browse files
authored
TypeDocumentNode support (2nd attempt) (#153)
* Remove getOperation function * Fix conflict * Fix exmaple app * Add a changeset * Remove TData * Modified changeset * TypedDocumentNode support * Add a changeset * Add type variables to FabrixComponent * Type rootFieldName argument * Fix type error * Relax type limitation * Ignore __typename * Fix __typename ignorance again * Fix typing on variables * Fix type errors * Modified changeset
1 parent d474f8c commit 7c104b0

File tree

6 files changed

+111
-35
lines changed

6 files changed

+111
-35
lines changed

.changeset/orange-peaches-doubt.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@fabrix-framework/fabrix": minor
3+
---
4+
5+
Add `TypedDocumentNode` support: now when the `TypedDocumentNode` query is given, `data` and `variables` are typed.
6+
7+
Also, `getComponent` has the first argument that is typed to help users select the component associated to the query.

packages/fabrix/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"@fabrix-framework/eslint-config": "workspace:*",
5959
"@fabrix-framework/prettier-config": "workspace:*",
6060
"@faker-js/faker": "^9.4.0",
61+
"@graphql-typed-document-node/core": "^3.2.0",
6162
"@testing-library/jest-dom": "^6.6.3",
6263
"@testing-library/react": "^16.2.0",
6364
"@testing-library/user-event": "^14.5.2",

packages/fabrix/src/fetcher.ts

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
import { DocumentNode } from "graphql";
2-
import { useClient, useQuery } from "urql";
2+
import { AnyVariables, useClient, useQuery } from "urql";
33

4-
export const useDataFetch = (props: {
4+
export const useDataFetch = <
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
TData = any,
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
TVariables = Record<string, any>,
9+
>(props: {
510
query: DocumentNode | string;
6-
variables?: Record<string, unknown>;
11+
variables: TVariables | undefined;
712
pause?: boolean;
813
}) => {
9-
const [{ data, fetching, error }] = useQuery<FabrixComponentData>({
10-
...props,
14+
const [{ data, fetching, error }] = useQuery<TData>({
15+
query: props.query,
16+
variables: props.variables as AnyVariables,
17+
pause: props.pause,
1118
});
1219

1320
return {

packages/fabrix/src/renderer.tsx

+63-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { DirectiveNode, DocumentNode, OperationTypeNode, parse } from "graphql";
1+
import { DirectiveNode, DocumentNode, OperationTypeNode } from "graphql";
22
import { ReactNode, useContext, useMemo } from "react";
33
import { findDirective, parseDirectiveArguments } from "@directive";
44
import { ViewRenderer } from "@renderers/fields";
@@ -9,7 +9,11 @@ import { directiveSchemaMap } from "@directive/schema";
99
import { mergeFieldConfigs } from "@readers/shared";
1010
import { buildDefaultViewFieldConfigs, viewFieldMerger } from "@readers/field";
1111
import { buildDefaultFormFieldConfigs, formFieldMerger } from "@readers/form";
12-
import { buildRootDocument, FieldVariables } from "@/visitor";
12+
import {
13+
buildRootDocument,
14+
FieldVariables,
15+
GeneralDocumentType,
16+
} from "@/visitor";
1317
import { Field, Fields } from "@/visitor/fields";
1418
import { FabrixComponentData, useDataFetch, Value } from "@/fetcher";
1519

@@ -105,10 +109,15 @@ export type FieldConfigs = {
105109
fields: FieldConfig[];
106110
};
107111

108-
export const useFieldConfigs = (query: DocumentNode | string) => {
109-
const rootDocument = buildRootDocument(
110-
typeof query === "string" ? parse(query) : query,
111-
);
112+
export const useFieldConfigs = <
113+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114+
TData = any,
115+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
116+
TVariables = Record<string, any>,
117+
>(
118+
query: GeneralDocumentType<TData, TVariables>,
119+
) => {
120+
const rootDocument = buildRootDocument(query);
112121
const context = useContext(FabrixContext);
113122
const fieldConfigs = useMemo(() => {
114123
return rootDocument.map(({ name, document, fields, opType, variables }) =>
@@ -142,11 +151,14 @@ export const useFieldConfigs = (query: DocumentNode | string) => {
142151
return { fieldConfigs };
143152
};
144153

145-
type FabrixComponentCommonProps = {
154+
type FabrixComponentCommonProps<
155+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
156+
TVariables = Record<string, any>,
157+
> = {
146158
/**
147159
* The variables to call the query with.
148160
*/
149-
variables?: Record<string, unknown>;
161+
variables?: TVariables;
150162

151163
/**
152164
* The title of the query.
@@ -164,7 +176,12 @@ type FabrixComponentCommonProps = {
164176
contentClassName?: string;
165177
};
166178

167-
export type FabrixComponentProps = FabrixComponentCommonProps & {
179+
export type FabrixComponentProps<
180+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
181+
TData = any,
182+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
183+
TVariables = Record<string, any>,
184+
> = FabrixComponentCommonProps<TVariables> & {
168185
/**
169186
* The query to render.
170187
*
@@ -178,19 +195,21 @@ export type FabrixComponentProps = FabrixComponentCommonProps & {
178195
* }
179196
* ```
180197
*/
181-
query: DocumentNode | string;
198+
query: GeneralDocumentType<TData, TVariables>;
182199

183-
children?: (props: FabrixComponentChildrenProps) => ReactNode;
200+
children?: (props: FabrixComponentChildrenProps<TData>) => ReactNode;
184201
};
185202

186203
type FabrixComponentChildrenExtraProps = { key?: string; className?: string };
187204

188-
export type FabrixComponentChildrenProps = {
205+
export type FabrixComponentChildrenProps<
206+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
207+
TData = any,
208+
> = {
189209
/**
190210
* The data fetched from the query
191211
*/
192-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
193-
data: any;
212+
data: TData;
194213

195214
/**
196215
* Get the component by root field name
@@ -204,7 +223,9 @@ export type FabrixComponentChildrenProps = {
204223
* ```
205224
*/
206225
getComponent: (
207-
rootFieldName: string,
226+
rootFieldName: TData extends Record<string, unknown>
227+
? Exclude<Extract<keyof TData, string>, "__typename">
228+
: string,
208229
extraProps?: FabrixComponentChildrenExtraProps,
209230
fieldsRenderer?: FabrixComponentFieldsRenderer,
210231
) => ReactNode;
@@ -232,7 +253,14 @@ export type FabrixComponentChildrenProps = {
232253
* </FabrixComponent>
233254
* ```
234255
*/
235-
export const FabrixComponent = (props: FabrixComponentProps) => {
256+
export const FabrixComponent = <
257+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
258+
TData = any,
259+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
260+
TVariables = Record<string, any>,
261+
>(
262+
props: FabrixComponentProps<TData, TVariables>,
263+
) => {
236264
const renderComponent = getComponentRendererFn(
237265
props,
238266
getComponentFn(
@@ -270,8 +298,13 @@ export const FabrixComponent = (props: FabrixComponentProps) => {
270298
return <div className="fabrix wrapper">{renderComponent()}</div>;
271299
};
272300

273-
export const getComponentRendererFn = (
274-
props: FabrixComponentProps,
301+
export const getComponentRendererFn = <
302+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
303+
TData = any,
304+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
305+
TVariables = Record<string, any>,
306+
>(
307+
props: FabrixComponentProps<TData, TVariables>,
275308
getComponent: ReturnType<typeof getComponentFn>,
276309
) => {
277310
const context = useContext(FabrixContext);
@@ -282,7 +315,7 @@ export const getComponentRendererFn = (
282315
}
283316

284317
return () => {
285-
const { fetching, error, data } = useDataFetch({
318+
const { fetching, error, data } = useDataFetch<TData, TVariables>({
286319
query: fieldConfig.document,
287320
variables: props.variables,
288321
pause: fieldConfig.type !== OperationTypeNode.QUERY,
@@ -296,10 +329,10 @@ export const getComponentRendererFn = (
296329
throw error;
297330
}
298331

299-
const component = getComponent(fieldConfig, data, context);
332+
const component = getComponent(fieldConfig, data ?? {}, context);
300333
if (props.children) {
301334
return props.children({
302-
data,
335+
data: data ?? ({} as TData),
303336
getComponent: component,
304337
});
305338
}
@@ -320,7 +353,15 @@ type RendererFn = (
320353
) => ReactNode;
321354

322355
export const getComponentFn =
323-
(props: FabrixComponentProps, rendererFn: RendererFn) =>
356+
<
357+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
358+
TData = any,
359+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
360+
TVariables = Record<string, any>,
361+
>(
362+
props: FabrixComponentProps<TData, TVariables>,
363+
rendererFn: RendererFn,
364+
) =>
324365
(
325366
fieldConfig: FieldConfigs,
326367
data: FabrixComponentData | undefined,

packages/fabrix/src/visitor.ts

+18-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Fields, SelectionField } from "@visitor/fields";
2+
import { TypedDocumentNode } from "@graphql-typed-document-node/core";
23
import {
34
DirectiveNode,
45
DocumentNode,
@@ -26,15 +27,28 @@ type S = {
2627
fields: Fields;
2728
};
2829

29-
export const buildRootDocument = (document: DocumentNode) =>
30-
document.definitions.map((def) =>
30+
export type GeneralDocumentType<
31+
TData = unknown,
32+
TVariables = Record<string, unknown>,
33+
> = string | DocumentNode | TypedDocumentNode<TData, TVariables>;
34+
35+
export const buildRootDocument = <
36+
TData = unknown,
37+
TVariables = Record<string, unknown>,
38+
>(
39+
document: GeneralDocumentType<TData, TVariables>,
40+
) => {
41+
const parsedDocument =
42+
typeof document === "string" ? parse(document) : document;
43+
return parsedDocument.definitions.map((def) =>
3144
buildQueryStructure({
3245
kind: Kind.DOCUMENT,
3346
definitions: [def],
3447
}),
3548
);
49+
};
3650

37-
const buildQueryStructure = (ast: DocumentNode | string) => {
51+
const buildQueryStructure = (ast: DocumentNode) => {
3852
const operationStructure = {} as S;
3953

4054
const extractTypeNode = (node: TypeNode) => {
@@ -51,7 +65,7 @@ const buildQueryStructure = (ast: DocumentNode | string) => {
5165
};
5266

5367
const currentPath: string[] = [];
54-
visit(typeof ast === "string" ? parse(ast) : ast, {
68+
visit(ast, {
5569
OperationDefinition: (node) => {
5670
// No fragment support currently
5771
if (

pnpm-lock.yaml

+10-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)