-
-
Notifications
You must be signed in to change notification settings - Fork 124
/
use-masked-errors.ts
125 lines (109 loc) · 3.4 KB
/
use-masked-errors.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { ExecutionResult, Plugin } from '@envelop/types';
import { handleStreamOrSingleExecutionResult } from '../utils.js';
export const DEFAULT_ERROR_MESSAGE = 'Unexpected error.';
export type MaskError = (error: unknown, message: string) => Error;
export type SerializableGraphQLErrorLike = Error & {
name: 'GraphQLError';
toJSON(): { message: string };
extensions?: Record<string, unknown>;
};
export function isGraphQLError(error: unknown): error is Error & { originalError?: Error } {
return error instanceof Error && error.name === 'GraphQLError';
}
export function isOriginalGraphQLError(error: unknown): error is Error & { originalError?: Error } {
if (isGraphQLError(error)) {
if (error.originalError != null) {
return isOriginalGraphQLError(error.originalError);
}
return true;
}
return false;
}
function createSerializableGraphQLError(
message: string,
originalError: unknown,
isDev: boolean,
): SerializableGraphQLErrorLike {
const error = new Error(message) as SerializableGraphQLErrorLike;
error.name = 'GraphQLError';
if (isDev) {
const extensions =
originalError instanceof Error
? { message: originalError.message, stack: originalError.stack }
: { message: String(originalError) };
Object.defineProperty(error, 'extensions', {
get() {
return extensions;
},
});
}
Object.defineProperty(error, 'toJSON', {
value() {
return {
message: error.message,
extensions: error.extensions,
};
},
});
return error as SerializableGraphQLErrorLike;
}
export const createDefaultMaskError =
(isDev: boolean): MaskError =>
(error, message) => {
if (isOriginalGraphQLError(error)) {
return error;
}
return createSerializableGraphQLError(message, error, isDev);
};
const isDev = globalThis.process?.env?.NODE_ENV === 'development';
export const defaultMaskError: MaskError = createDefaultMaskError(isDev);
export type UseMaskedErrorsOpts = {
/** The function used for identify and mask errors. */
maskError?: MaskError;
/** The error message that shall be used for masked errors. */
errorMessage?: string;
};
const makeHandleResult =
(maskError: MaskError, message: string) =>
({
result,
setResult,
}: {
result: ExecutionResult;
setResult: (result: ExecutionResult) => void;
}) => {
if (result.errors != null) {
setResult({ ...result, errors: result.errors.map(error => maskError(error, message)) });
}
};
export function useMaskedErrors<PluginContext extends Record<string, any> = {}>(
opts?: UseMaskedErrorsOpts,
): Plugin<PluginContext> {
const maskError = opts?.maskError ?? defaultMaskError;
const message = opts?.errorMessage || DEFAULT_ERROR_MESSAGE;
const handleResult = makeHandleResult(maskError, message);
return {
onPluginInit(context) {
context.registerContextErrorHandler(({ error, setError }) => {
setError(maskError(error, message));
});
},
onExecute() {
return {
onExecuteDone(payload) {
return handleStreamOrSingleExecutionResult(payload, handleResult);
},
};
},
onSubscribe() {
return {
onSubscribeResult(payload) {
return handleStreamOrSingleExecutionResult(payload, handleResult);
},
onSubscribeError({ error, setError }) {
setError(maskError(error, message));
},
};
},
};
}