Skip to content

Commit a9502fc

Browse files
authored
feat(package/gqty): Use GraphQLError over plain objects in defaultResponseHandler (#2058)
* feat(package/gqty): Use GraphQLError over plain objects in defaultResponseHandler * chore(example): Update GQty clients in examples to use defaultResponseHandler * fix(package/solid): flaky tests
1 parent e41dfea commit a9502fc

File tree

13 files changed

+588
-145
lines changed

13 files changed

+588
-145
lines changed

.changeset/angry-peaches-peel.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gqty': minor
3+
---
4+
5+
Use GraphQLError over plain objects in defaultResponseHandler

.vscode/settings.json

+3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
2+
"[javascript][typescript][typescriptreact]": {
3+
"editor.defaultFormatter": "esbenp.prettier-vscode"
4+
},
25
"typescript.tsdk": "node_modules/typescript/lib",
36
"prettier.proseWrap": "preserve"
47
}

examples/gnt/gqty/index.ts

+8-19
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
*/
44

55
import { createLogger } from '@gqty/logger';
6-
import { Cache, GQtyError, createClient, type QueryFetcher } from 'gqty';
76
import {
7+
Cache,
8+
createClient,
9+
defaultResponseHandler,
10+
type QueryFetcher,
11+
} from 'gqty';
12+
import {
13+
type GeneratedSchema,
814
generatedSchema,
915
scalarsEnumsHash,
10-
type GeneratedSchema,
1116
} from './schema.generated';
1217

1318
const queryFetcher: QueryFetcher = async function (
@@ -29,23 +34,7 @@ const queryFetcher: QueryFetcher = async function (
2934
...fetchOptions,
3035
});
3136

32-
if (response.status >= 400) {
33-
throw new GQtyError(
34-
`GraphQL endpoint responded with HTTP status ${response.status}.`
35-
);
36-
}
37-
38-
const text = await response.text();
39-
40-
try {
41-
return JSON.parse(text);
42-
} catch {
43-
throw new GQtyError(
44-
`Malformed JSON response: ${
45-
text.length > 50 ? text.slice(0, 50) + '...' : text
46-
}`
47-
);
48-
}
37+
return await defaultResponseHandler(response);
4938
};
5039

5140
const cache = new Cache(

examples/react/src/graphql/gqty.ts

+22-20
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
import { createSubscriptionsClient } from '@gqty/subscriptions';
77
import extractFiles from 'extract-files/extractFiles.mjs';
88
import isExtractableFile from 'extract-files/isExtractableFile.mjs';
9-
import { Cache, createClient, LegacyQueryFetcher as QueryFetcher } from 'gqty';
109
import {
11-
generatedSchema,
10+
Cache,
11+
createClient,
12+
defaultResponseHandler,
13+
LegacyQueryFetcher as QueryFetcher,
14+
} from 'gqty';
15+
import {
1216
GeneratedSchema,
17+
generatedSchema,
1318
scalarsEnumsHash,
14-
SchemaObjectTypes,
15-
SchemaObjectTypesNames,
1619
} from './schema.generated';
1720

1821
const queryFetcher: QueryFetcher = async function (query, variables) {
@@ -33,10 +36,13 @@ const queryFetcher: QueryFetcher = async function (query, variables) {
3336
formData.append(
3437
'map',
3538
JSON.stringify(
36-
[...files.values()].reduce((prev, paths, i) => {
37-
prev[i + 1] = paths;
38-
return prev;
39-
}, {} as Record<number, string[]>)
39+
[...files.values()].reduce(
40+
(prev, paths, i) => {
41+
prev[i + 1] = paths;
42+
return prev;
43+
},
44+
{} as Record<number, string[]>
45+
)
4046
)
4147
);
4248

@@ -51,7 +57,7 @@ const queryFetcher: QueryFetcher = async function (query, variables) {
5157
mode: 'cors',
5258
});
5359

54-
return await response.json();
60+
return await defaultResponseHandler(response);
5561
} else {
5662
const response = await fetch(endpoint, {
5763
method: 'POST',
@@ -65,13 +71,14 @@ const queryFetcher: QueryFetcher = async function (query, variables) {
6571
mode: 'cors',
6672
});
6773

68-
return await response.json();
74+
return await defaultResponseHandler(response);
6975
}
7076
};
7177

7278
const subscriptionsClient =
73-
typeof window !== 'undefined'
74-
? createSubscriptionsClient({
79+
typeof window === 'undefined'
80+
? undefined
81+
: createSubscriptionsClient({
7582
wsEndpoint: () => {
7683
// Modify if needed
7784
const url = new URL('/api/graphql', window.location.href);
@@ -80,20 +87,15 @@ const subscriptionsClient =
8087
console.log(42, url.href);
8188
return url.href;
8289
},
83-
})
84-
: undefined;
90+
});
8591

8692
export const cache = new Cache(undefined, {
8793
maxAge: 1000,
8894
staleWhileRevalidate: 60 * 5000,
8995
normalization: true,
9096
});
9197

92-
export const client = createClient<
93-
GeneratedSchema,
94-
SchemaObjectTypesNames,
95-
SchemaObjectTypes
96-
>({
98+
export const client = createClient<GeneratedSchema>({
9799
cache,
98100
schema: generatedSchema,
99101
scalarsEnumsHash,
@@ -105,4 +107,4 @@ const { query, mutation, mutate, subscription, resolved, refetch, track } =
105107
client;
106108

107109
export * from './schema.generated';
108-
export { query, mutation, mutate, subscription, resolved, refetch, track };
110+
export { mutate, mutation, query, refetch, resolved, subscription, track };

examples/solid/src/gqty/index.ts

+9-13
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
*/
44

55
import { createSolidClient } from '@gqty/solid';
6-
import { Cache, GQtyError, createClient, type QueryFetcher } from 'gqty';
76
import {
7+
Cache,
8+
createClient,
9+
defaultResponseHandler,
10+
GQtyError,
11+
type QueryFetcher,
12+
} from 'gqty';
13+
import {
14+
type GeneratedSchema,
815
generatedSchema,
916
scalarsEnumsHash,
10-
type GeneratedSchema,
1117
} from './schema.generated';
1218

1319
const queryFetcher: QueryFetcher = async function (
@@ -35,17 +41,7 @@ const queryFetcher: QueryFetcher = async function (
3541
);
3642
}
3743

38-
const text = await response.text();
39-
40-
try {
41-
return JSON.parse(text);
42-
} catch {
43-
throw new GQtyError(
44-
`Malformed JSON response: ${
45-
text.length > 50 ? text.slice(0, 50) + '...' : text
46-
}`
47-
);
48-
}
44+
return await defaultResponseHandler(response);
4945
};
5046

5147
const cache = new Cache(

examples/vite-react/src/gqty/index.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import { createReactClient } from '@gqty/react';
66
import type { QueryFetcher } from 'gqty';
7-
import { Cache, createClient } from 'gqty';
7+
import { Cache, createClient, defaultResponseHandler } from 'gqty';
88
import type { GeneratedSchema } from './schema.generated';
99
import { generatedSchema, scalarsEnumsHash } from './schema.generated';
1010

@@ -27,9 +27,7 @@ const queryFetcher: QueryFetcher = async function (
2727
...fetchOptions,
2828
});
2929

30-
const json = await response.json();
31-
32-
return json;
30+
return await defaultResponseHandler(response);
3331
};
3432

3533
const cache = new Cache(

packages/cli/build.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fs.rmSync('dist', { force: true, recursive: true });
1010
// tsc --emitDeclarationOnly
1111
// Note: Creates the target directory, and serves as a pre-build typecheck.
1212
tsc
13-
.createProgram(['src/index.ts'], {
13+
.createProgram(['src/envelop.ts', 'src/index.ts'], {
1414
baseUrl: '.',
1515
declaration: true,
1616
emitDeclarationOnly: true,

packages/cli/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
"types": "./dist/index.d.ts",
2626
"import": "./dist/index.mjs",
2727
"require": "./dist/index.js"
28+
},
29+
"./envelop": {
30+
"types": "./dist/envelop.d.ts",
31+
"import": "./dist/envelop.mjs",
32+
"require": "./dist/envelop.js"
2833
}
2934
},
3035
"files": [

packages/gqty/src/Cache/normalization.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ export const normalizeObject = <TData extends CacheObject>(
8383

8484
shells.add(result);
8585

86-
store.set(id, result);
86+
store.set(id, result, {
87+
// Has to use strong reference as this is usually the only reference,
88+
// the cache is referencing the object via an internal identifier string.
89+
strong: true,
90+
});
8791

8892
return result;
8993
}

packages/gqty/src/Helpers/fetcher.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ExecutionResult } from 'graphql';
1+
import { GraphQLError, type ExecutionResult } from 'graphql';
22
import { GQtyError } from '../Error';
33

44
export const defaultResponseHandler = async (response: Response) => {
@@ -29,7 +29,15 @@ export const parseResponse = async (response: Response) => {
2929
}
3030

3131
try {
32-
return JSON.parse(text);
32+
const result = JSON.parse(text);
33+
34+
if (Array.isArray(result?.errors)) {
35+
result.errors = result.errors.map(
36+
(error: any) => new GraphQLError(error.message, error)
37+
);
38+
}
39+
40+
return result;
3341
} catch {
3442
throw new GQtyError(
3543
`Received malformed JSON response from GraphQL endpoint: ${
@@ -56,7 +64,11 @@ export const isExecutionResult = (input: unknown): input is ExecutionResult => {
5664

5765
const value = input as Record<string, unknown>;
5866

59-
return 'data' in value || Array.isArray(value.errors);
67+
return (
68+
'data' in value ||
69+
(Array.isArray(value.errors) &&
70+
value.errors.every((error) => error instanceof GraphQLError))
71+
);
6072
};
6173

6274
export const handleResponseErrors = (result: ExecutionResult) => {

packages/react/test/useMutation.test.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { renderHook, waitFor } from '@testing-library/react';
2+
import { Cache } from 'gqty';
23
import { act } from 'react';
34
import { createReactTestClient } from './utils';
45

@@ -65,7 +66,17 @@ describe('useMutation', () => {
6566
});
6667

6768
it('should update contents of useQuery via normalized cache', async () => {
68-
const { useQuery, useMutation } = await createReactTestClient();
69+
const { useQuery, useMutation } = await createReactTestClient(
70+
undefined,
71+
undefined,
72+
undefined,
73+
{
74+
cache: new Cache(undefined, {
75+
maxAge: Infinity,
76+
normalization: true,
77+
}),
78+
}
79+
);
6980
const { result: q } = renderHook(() => {
7081
const human = useQuery().human({ name: 'Uno' });
7182

packages/solid/src/query.test.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('createQuery', () => {
2121
const { createQuery } = await createMockSolidClient({
2222
client: {
2323
cache: new Cache(undefined, {
24-
maxAge: 0,
24+
maxAge: 100,
2525
staleWhileRevalidate: 5 * 30 * 1000,
2626
}),
2727
onFetch: ({ query }) => {
@@ -366,7 +366,7 @@ describe('createQuery', () => {
366366
const { createQuery } = await createMockSolidClient({
367367
client: {
368368
cache: new Cache(undefined, {
369-
maxAge: 0,
369+
maxAge: 100,
370370
staleWhileRevalidate: 5 * 30 * 1000,
371371
}),
372372
},
@@ -430,7 +430,7 @@ describe('createQuery', () => {
430430
const { createQuery } = await createMockSolidClient({
431431
client: {
432432
cache: new Cache(undefined, {
433-
maxAge: 0,
433+
maxAge: 100,
434434
staleWhileRevalidate: 5 * 30 * 1000,
435435
}),
436436
},

0 commit comments

Comments
 (0)