@pcg/graphql-kit 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-lint.log +4 -0
- package/CHANGELOG.md +7 -0
- package/eslint.config.js +5 -0
- package/package.json +32 -0
- package/src/graphql-error-log.ts +105 -0
- package/src/index.ts +3 -0
- package/src/tools.ts +20 -0
- package/src/types.ts +32 -0
- package/tests/graphql-error-log.test.ts +219 -0
- package/tests/tools.test.ts +152 -0
- package/tsconfig.json +9 -0
- package/tsconfig.lib.json +9 -0
- package/tsdown.config.ts +11 -0
- package/vitest.config.ts +19 -0
package/CHANGELOG.md
ADDED
package/eslint.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pcg/graphql-kit",
|
|
3
|
+
"version": "1.0.0-alpha.1",
|
|
4
|
+
"description": "GraphQL utilities and error handling for NestJS Apollo",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"email": "code@deepvision.team",
|
|
8
|
+
"name": "DeepVision Code"
|
|
9
|
+
},
|
|
10
|
+
"contributors": [
|
|
11
|
+
"Vitaliy Angolenko <v.angolenko@deepvision.software>",
|
|
12
|
+
"Sergii Sadovyi <s.sadovyi@deepvision.software>"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@apollo/client": "^3.13.0",
|
|
19
|
+
"graphql": "^16.10.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@vitest/ui": "^3.2.4",
|
|
23
|
+
"vitest": "^3.2.4"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"dev": "tsdown --watch",
|
|
27
|
+
"build": "tsdown",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"test:watch": "vitest",
|
|
30
|
+
"lint": "eslint \"src/**/*.ts\" --fix"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type ApolloError } from '@apollo/client/core';
|
|
2
|
+
import { type ErrorResponse } from '@apollo/client/link/error';
|
|
3
|
+
import { print } from 'graphql';
|
|
4
|
+
|
|
5
|
+
import { isNestJsGraphQLError } from './tools';
|
|
6
|
+
|
|
7
|
+
export const getErrorMessages = (error: ErrorResponse | ApolloError) => {
|
|
8
|
+
const messages: string[] = [];
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- Safe to use in Apollo Client 3.x
|
|
10
|
+
const { graphQLErrors, networkError } = error;
|
|
11
|
+
const operation = 'operation' in error ? error.operation : undefined;
|
|
12
|
+
const stack = 'stack' in error ? error.stack : undefined;
|
|
13
|
+
let printedQuery: string;
|
|
14
|
+
|
|
15
|
+
if (operation) {
|
|
16
|
+
printedQuery = print(operation.query);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (graphQLErrors) {
|
|
20
|
+
graphQLErrors.forEach((error) => {
|
|
21
|
+
if (isNestJsGraphQLError(error)) {
|
|
22
|
+
messages.push(`[NestJs GraphQL Error] ${error.message} (${error.extensions.key})`);
|
|
23
|
+
} else {
|
|
24
|
+
messages.push(`[GraphQL Error] ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (operation) {
|
|
28
|
+
messages.push(logOperation(printedQuery, error.locations));
|
|
29
|
+
if (Object.keys(operation.variables).length) {
|
|
30
|
+
messages.push(`with variables: ${JSON.stringify(operation.variables, null, 2)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (isNestJsGraphQLError(error)) {
|
|
35
|
+
messages.push(`with context: ${JSON.stringify(error.extensions.context, null, 2)}`);
|
|
36
|
+
|
|
37
|
+
if (error.extensions.errorStack) {
|
|
38
|
+
messages.push(`with stack: ${JSON.stringify(error.extensions.errorStack, null, 2)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (networkError) {
|
|
45
|
+
messages.push(`[GraphQL Network Error] ${networkError}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (stack) {
|
|
49
|
+
messages.push(stack);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return messages;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const logErrorMessages = (error: ApolloError | ErrorResponse, printStack = true) => {
|
|
56
|
+
getErrorMessages(error).forEach((message) => {
|
|
57
|
+
const result = /\[([\w ]*)](.*)/.exec(message);
|
|
58
|
+
if (result) {
|
|
59
|
+
const [, tag, msg] = result;
|
|
60
|
+
console.log(`%c${tag}`, 'color:white;border-radius:3px;background:#ff4400;font-weight:bold;padding:2px 6px;', msg);
|
|
61
|
+
} else {
|
|
62
|
+
console.log(message);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (printStack) {
|
|
67
|
+
let stack = new Error().stack;
|
|
68
|
+
if (stack == null) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const newLineIndex = stack.indexOf('\n');
|
|
73
|
+
stack = stack.slice(stack.indexOf('\n', newLineIndex + 1));
|
|
74
|
+
console.log(`%c${stack}`, 'color:grey;');
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
interface ErrorLocation {
|
|
79
|
+
line: number;
|
|
80
|
+
column: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const logOperation = (printedQuery: string, locations: readonly ErrorLocation[] | undefined) => {
|
|
84
|
+
const lines = printedQuery.split('\n');
|
|
85
|
+
const l = lines.length;
|
|
86
|
+
const result = lines.slice();
|
|
87
|
+
const lineMap: Record<number, number> = {
|
|
88
|
+
};
|
|
89
|
+
for (let i = 0; i < l; i++) {
|
|
90
|
+
lineMap[i] = i;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (locations) {
|
|
94
|
+
for (const { line, column } of locations) {
|
|
95
|
+
const index = lineMap[line];
|
|
96
|
+
result.splice(index, 0, '▲'.padStart(column, ' '));
|
|
97
|
+
// Offset remaining lines
|
|
98
|
+
for (let i = index + 1; i < l; i++) {
|
|
99
|
+
lineMap[i]++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return result.join('\n');
|
|
105
|
+
};
|
package/src/index.ts
ADDED
package/src/tools.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type GraphQLFormattedError } from 'graphql';
|
|
2
|
+
|
|
3
|
+
import { type NestJsApolloError, type NestJsGraphQLError } from './types';
|
|
4
|
+
|
|
5
|
+
export const isNestJsApolloError = (err: GraphQLFormattedError): err is NestJsApolloError => {
|
|
6
|
+
return Object.prototype.hasOwnProperty.call(err, 'graphQLErrors');
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const isNestJsGraphQLError = (err: GraphQLFormattedError): err is NestJsGraphQLError => {
|
|
10
|
+
const nestError = err as NestJsGraphQLError;
|
|
11
|
+
return Boolean(nestError.extensions) && Boolean(nestError.extensions.context);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const getFirstNestJsGraphQLError = (err: GraphQLFormattedError): NestJsGraphQLError | null => {
|
|
15
|
+
if (!isNestJsApolloError(err)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return err.graphQLErrors[0] ?? null;
|
|
20
|
+
};
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ApolloError } from '@apollo/client/core';
|
|
2
|
+
import { GraphQLError } from 'graphql';
|
|
3
|
+
|
|
4
|
+
export interface ErrorStackItem {
|
|
5
|
+
key: string;
|
|
6
|
+
message: string;
|
|
7
|
+
context: Record<string, unknown>;
|
|
8
|
+
stacktrace?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class NestJsGraphQLError extends GraphQLError {
|
|
12
|
+
declare extensions: {
|
|
13
|
+
context: {
|
|
14
|
+
scope?: string;
|
|
15
|
+
action?: string;
|
|
16
|
+
requestId?: string;
|
|
17
|
+
userId?: string;
|
|
18
|
+
[attributeName: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
key: string;
|
|
21
|
+
statusCode: number;
|
|
22
|
+
errorStack?: ErrorStackItem[];
|
|
23
|
+
format: 'NESTJS';
|
|
24
|
+
[attributeName: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type NestJsGraphQLErrors = readonly NestJsGraphQLError[];
|
|
29
|
+
|
|
30
|
+
export class NestJsApolloError extends ApolloError {
|
|
31
|
+
declare graphQLErrors: NestJsGraphQLErrors;
|
|
32
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { GraphQLError } from 'graphql';
|
|
3
|
+
import {
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
} from 'vitest';
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
getErrorMessages,
|
|
12
|
+
logErrorMessages,
|
|
13
|
+
} from '@/graphql-error-log.js';
|
|
14
|
+
import { NestJsGraphQLError } from '@/types.js';
|
|
15
|
+
|
|
16
|
+
describe('graphql-error-log', () => {
|
|
17
|
+
describe('getErrorMessages', () => {
|
|
18
|
+
it('should return empty array for error without graphQLErrors and networkError', () => {
|
|
19
|
+
const error: any = {
|
|
20
|
+
graphQLErrors: undefined,
|
|
21
|
+
networkError: undefined,
|
|
22
|
+
};
|
|
23
|
+
const messages = getErrorMessages(error);
|
|
24
|
+
expect(messages).toEqual([]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should handle graphQLErrors', () => {
|
|
28
|
+
const error: any = {
|
|
29
|
+
graphQLErrors: [new GraphQLError('Test GraphQL Error')],
|
|
30
|
+
networkError: undefined,
|
|
31
|
+
};
|
|
32
|
+
const messages = getErrorMessages(error);
|
|
33
|
+
expect(messages).toContain('[GraphQL Error] Test GraphQL Error');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should handle NestJsGraphQLError with key', () => {
|
|
37
|
+
const nestError = new NestJsGraphQLError('NestJs Error', {
|
|
38
|
+
extensions: {
|
|
39
|
+
context: {
|
|
40
|
+
userId: '123',
|
|
41
|
+
},
|
|
42
|
+
key: 'CUSTOM_ERROR',
|
|
43
|
+
statusCode: 400,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const error: any = {
|
|
47
|
+
graphQLErrors: [nestError],
|
|
48
|
+
networkError: undefined,
|
|
49
|
+
};
|
|
50
|
+
const messages = getErrorMessages(error);
|
|
51
|
+
expect(messages.some((msg: string) => msg.includes('[NestJs GraphQL Error]'))).toBe(true);
|
|
52
|
+
expect(messages.some((msg: string) => msg.includes('CUSTOM_ERROR'))).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle network errors', () => {
|
|
56
|
+
const networkError = new Error('Network failure');
|
|
57
|
+
const error: any = {
|
|
58
|
+
graphQLErrors: undefined,
|
|
59
|
+
networkError,
|
|
60
|
+
};
|
|
61
|
+
const messages = getErrorMessages(error);
|
|
62
|
+
expect(messages).toContain('[GraphQL Network Error] Error: Network failure');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle both graphQLErrors and networkError', () => {
|
|
66
|
+
const graphqlError = new GraphQLError('GraphQL Error');
|
|
67
|
+
const networkError = new Error('Network Error');
|
|
68
|
+
const error: any = {
|
|
69
|
+
graphQLErrors: [graphqlError],
|
|
70
|
+
networkError,
|
|
71
|
+
};
|
|
72
|
+
const messages = getErrorMessages(error);
|
|
73
|
+
expect(messages.some((msg: string) => msg.includes('GraphQL Error'))).toBe(true);
|
|
74
|
+
expect(messages.some((msg: string) => msg.includes('Network Error'))).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle multiple graphQLErrors', () => {
|
|
78
|
+
const error: any = {
|
|
79
|
+
graphQLErrors: [
|
|
80
|
+
new GraphQLError('Error 1'),
|
|
81
|
+
new GraphQLError('Error 2'),
|
|
82
|
+
new GraphQLError('Error 3'),
|
|
83
|
+
],
|
|
84
|
+
networkError: undefined,
|
|
85
|
+
};
|
|
86
|
+
const messages = getErrorMessages(error);
|
|
87
|
+
expect(messages.filter((msg: string) => msg.includes('[GraphQL Error]')).length).toBe(3);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should include context for NestJsGraphQLError', () => {
|
|
91
|
+
const nestError = new NestJsGraphQLError('NestJs Error', {
|
|
92
|
+
extensions: {
|
|
93
|
+
context: {
|
|
94
|
+
userId: '123',
|
|
95
|
+
scope: 'admin',
|
|
96
|
+
},
|
|
97
|
+
key: 'AUTH_ERROR',
|
|
98
|
+
statusCode: 401,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const error: any = {
|
|
102
|
+
graphQLErrors: [nestError],
|
|
103
|
+
networkError: undefined,
|
|
104
|
+
};
|
|
105
|
+
const messages = getErrorMessages(error);
|
|
106
|
+
expect(messages.some((msg: string) => msg.includes('with context'))).toBe(true);
|
|
107
|
+
expect(messages.some((msg: string) => msg.includes('userId'))).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should include errorStack for NestJsGraphQLError', () => {
|
|
111
|
+
const nestError = new NestJsGraphQLError('NestJs Error', {
|
|
112
|
+
extensions: {
|
|
113
|
+
context: {
|
|
114
|
+
userId: '123',
|
|
115
|
+
},
|
|
116
|
+
key: 'ERROR',
|
|
117
|
+
statusCode: 400,
|
|
118
|
+
errorStack: [
|
|
119
|
+
{
|
|
120
|
+
key: 'VALIDATION_ERROR',
|
|
121
|
+
message: 'Invalid input',
|
|
122
|
+
context: {
|
|
123
|
+
field: 'email',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
const error: any = {
|
|
130
|
+
graphQLErrors: [nestError],
|
|
131
|
+
networkError: undefined,
|
|
132
|
+
};
|
|
133
|
+
const messages = getErrorMessages(error);
|
|
134
|
+
expect(messages.some((msg: string) => msg.includes('with stack'))).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('logErrorMessages', () => {
|
|
139
|
+
it('should call console.log for each message', () => {
|
|
140
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
141
|
+
const error: any = {
|
|
142
|
+
graphQLErrors: [new GraphQLError('Test Error')],
|
|
143
|
+
networkError: undefined,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
logErrorMessages(error, false);
|
|
147
|
+
|
|
148
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
149
|
+
consoleSpy.mockRestore();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should format error tags correctly', () => {
|
|
153
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
154
|
+
const error: any = {
|
|
155
|
+
graphQLErrors: [new GraphQLError('Test Error')],
|
|
156
|
+
networkError: undefined,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
logErrorMessages(error, false);
|
|
160
|
+
|
|
161
|
+
const calls = consoleSpy.mock.calls.map((call: any) => call[0]);
|
|
162
|
+
expect(calls.some((call: any) => typeof call === 'string' && call.includes('GraphQL Error'))).toBe(true);
|
|
163
|
+
|
|
164
|
+
consoleSpy.mockRestore();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should include stack trace when printStack is true', () => {
|
|
168
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
169
|
+
const error: any = {
|
|
170
|
+
graphQLErrors: [new GraphQLError('Test Error')],
|
|
171
|
+
networkError: undefined,
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
logErrorMessages(error, true);
|
|
175
|
+
|
|
176
|
+
const calls = consoleSpy.mock.calls;
|
|
177
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
178
|
+
|
|
179
|
+
consoleSpy.mockRestore();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should handle NestJsGraphQLError tags correctly', () => {
|
|
183
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
184
|
+
const nestError = new NestJsGraphQLError('NestJs Error', {
|
|
185
|
+
extensions: {
|
|
186
|
+
context: {
|
|
187
|
+
},
|
|
188
|
+
key: 'CUSTOM_ERROR',
|
|
189
|
+
statusCode: 400,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
const error: any = {
|
|
193
|
+
graphQLErrors: [nestError],
|
|
194
|
+
networkError: undefined,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
logErrorMessages(error, false);
|
|
198
|
+
|
|
199
|
+
const calls = consoleSpy.mock.calls.map((call: any) => call[0]);
|
|
200
|
+
expect(calls.some((call: any) => typeof call === 'string' && call.includes('NestJs GraphQL Error'))).toBe(true);
|
|
201
|
+
|
|
202
|
+
consoleSpy.mockRestore();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should handle default printStack value', () => {
|
|
206
|
+
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
207
|
+
const error: any = {
|
|
208
|
+
graphQLErrors: [new GraphQLError('Test Error')],
|
|
209
|
+
networkError: undefined,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
logErrorMessages(error);
|
|
213
|
+
|
|
214
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
215
|
+
|
|
216
|
+
consoleSpy.mockRestore();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { GraphQLError } from 'graphql';
|
|
3
|
+
import {
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
} from 'vitest';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
getFirstNestJsGraphQLError,
|
|
11
|
+
isNestJsApolloError,
|
|
12
|
+
isNestJsGraphQLError,
|
|
13
|
+
} from '@/tools.js';
|
|
14
|
+
import { NestJsGraphQLError } from '@/types.js';
|
|
15
|
+
|
|
16
|
+
type TestError = GraphQLError | Record<string, unknown>;
|
|
17
|
+
|
|
18
|
+
describe('tools', () => {
|
|
19
|
+
describe('isNestJsApolloError', () => {
|
|
20
|
+
it('should identify apollo errors with graphQLErrors property', () => {
|
|
21
|
+
const apolloError: TestError = {
|
|
22
|
+
message: 'test',
|
|
23
|
+
graphQLErrors: [],
|
|
24
|
+
};
|
|
25
|
+
expect(isNestJsApolloError(apolloError as any)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return false for objects without graphQLErrors property', () => {
|
|
29
|
+
const notApolloError: TestError = {
|
|
30
|
+
message: 'test',
|
|
31
|
+
};
|
|
32
|
+
expect(isNestJsApolloError(notApolloError as any)).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should return true for errors with non-empty graphQLErrors', () => {
|
|
36
|
+
const apolloError: TestError = {
|
|
37
|
+
message: 'Apollo Error',
|
|
38
|
+
graphQLErrors: [new GraphQLError('Internal error')],
|
|
39
|
+
};
|
|
40
|
+
expect(isNestJsApolloError(apolloError as any)).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('isNestJsGraphQLError', () => {
|
|
45
|
+
it('should identify graphql errors with extensions.context', () => {
|
|
46
|
+
const graphqlError: TestError = {
|
|
47
|
+
message: 'test',
|
|
48
|
+
extensions: {
|
|
49
|
+
context: {
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
expect(isNestJsGraphQLError(graphqlError as any)).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should return false for errors without extensions', () => {
|
|
57
|
+
const graphqlError: TestError = {
|
|
58
|
+
message: 'test',
|
|
59
|
+
};
|
|
60
|
+
expect(isNestJsGraphQLError(graphqlError as any)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should return false for errors without extensions.context', () => {
|
|
64
|
+
const graphqlError: TestError = {
|
|
65
|
+
message: 'test',
|
|
66
|
+
extensions: {
|
|
67
|
+
key: 'VALUE',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
expect(isNestJsGraphQLError(graphqlError as any)).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return true for errors with context and additional properties', () => {
|
|
74
|
+
const graphqlError: TestError = {
|
|
75
|
+
message: 'test',
|
|
76
|
+
extensions: {
|
|
77
|
+
context: {
|
|
78
|
+
scope: 'user',
|
|
79
|
+
action: 'login',
|
|
80
|
+
requestId: '123',
|
|
81
|
+
},
|
|
82
|
+
key: 'UNAUTHORIZED',
|
|
83
|
+
statusCode: 401,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
expect(isNestJsGraphQLError(graphqlError as any)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should handle errors with null context', () => {
|
|
90
|
+
const graphqlError: TestError = {
|
|
91
|
+
message: 'test',
|
|
92
|
+
extensions: {
|
|
93
|
+
context: null,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
expect(isNestJsGraphQLError(graphqlError as any)).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('getFirstNestJsGraphQLError', () => {
|
|
101
|
+
it('should return null if error is not apollo error', () => {
|
|
102
|
+
const error: TestError = {
|
|
103
|
+
message: 'test',
|
|
104
|
+
};
|
|
105
|
+
expect(getFirstNestJsGraphQLError(error as any)).toBeNull();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return null if graphQLErrors is empty', () => {
|
|
109
|
+
const error: TestError = {
|
|
110
|
+
message: 'test',
|
|
111
|
+
graphQLErrors: [],
|
|
112
|
+
};
|
|
113
|
+
expect(getFirstNestJsGraphQLError(error as any)).toBeNull();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should return first graphql error from apollo error', () => {
|
|
117
|
+
const firstError = new GraphQLError('First error');
|
|
118
|
+
const secondError = new GraphQLError('Second error');
|
|
119
|
+
const error: TestError = {
|
|
120
|
+
message: 'Apollo Error',
|
|
121
|
+
graphQLErrors: [firstError, secondError],
|
|
122
|
+
};
|
|
123
|
+
expect(getFirstNestJsGraphQLError(error as any)).toBe(firstError);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should return single graphql error from apollo error', () => {
|
|
127
|
+
const error = new GraphQLError('Single error');
|
|
128
|
+
const apolloError: TestError = {
|
|
129
|
+
message: 'Apollo Error',
|
|
130
|
+
graphQLErrors: [error],
|
|
131
|
+
};
|
|
132
|
+
expect(getFirstNestJsGraphQLError(apolloError as any)).toBe(error);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should handle NestJsGraphQLError specifically', () => {
|
|
136
|
+
const nestError = new NestJsGraphQLError('NestJs error', {
|
|
137
|
+
extensions: {
|
|
138
|
+
context: {
|
|
139
|
+
userId: '123',
|
|
140
|
+
},
|
|
141
|
+
key: 'CUSTOM_ERROR',
|
|
142
|
+
statusCode: 400,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const apolloError: TestError = {
|
|
146
|
+
message: 'Apollo Error',
|
|
147
|
+
graphQLErrors: [nestError],
|
|
148
|
+
};
|
|
149
|
+
expect(getFirstNestJsGraphQLError(apolloError as any)).toBe(nestError);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
package/tsconfig.json
ADDED
package/tsdown.config.ts
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// / <reference types="vitest" />
|
|
2
|
+
// eslint-disable-next-line node/no-unpublished-import
|
|
3
|
+
import { defineConfig } from 'vitest/config';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
test: {
|
|
7
|
+
environment: 'node',
|
|
8
|
+
include: ['tests/**/*.test.ts'],
|
|
9
|
+
globals: true,
|
|
10
|
+
typecheck: {
|
|
11
|
+
tsconfig: './tsconfig.json',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
resolve: {
|
|
15
|
+
alias: {
|
|
16
|
+
'@': new URL('./src', import.meta.url).pathname,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|