@@ -33,7 +33,7 @@ import type { HivePersistedDocumentsConfig, HiveUsageConfig } from './environmen
33
33
import { useArmor } from './use-armor' ;
34
34
import { extractUserId , useSentryUser } from './use-sentry-user' ;
35
35
36
- const reqIdGenerate = hyperid ( { fixedLength : true } ) ;
36
+ export const reqIdGenerate = hyperid ( { fixedLength : true } ) ;
37
37
38
38
function hashSessionId ( sessionId : string ) : string {
39
39
return createHash ( 'sha256' ) . update ( sessionId ) . digest ( 'hex' ) ;
@@ -56,7 +56,7 @@ export interface GraphQLHandlerOptions {
56
56
authN : AuthN ;
57
57
}
58
58
59
- interface Context extends RegistryContext {
59
+ export interface Context extends RegistryContext {
60
60
req : FastifyRequest ;
61
61
reply : FastifyReply ;
62
62
session : Session ;
@@ -80,6 +80,30 @@ function hasFastifyRequest(ctx: unknown): ctx is {
80
80
return ! ! ctx && typeof ctx === 'object' && 'req' in ctx ;
81
81
}
82
82
83
+ export function useHiveErrorHandler ( fallbackHandler : ( err : Error ) => void ) : Plugin {
84
+ return useErrorHandler ( ( { errors, context } ) : void => {
85
+ // Not sure what changed, but the `context` is now an object with a contextValue property.
86
+ // We previously relied on the `context` being the `contextValue` itself.
87
+ const ctx = ( 'contextValue' in context ? context . contextValue : context ) as Context ;
88
+
89
+ for ( const error of errors ) {
90
+ if ( isGraphQLError ( error ) && error . originalError ) {
91
+ console . error ( error ) ;
92
+ console . error ( error . originalError ) ;
93
+ continue ;
94
+ } else {
95
+ console . error ( error ) ;
96
+ }
97
+
98
+ if ( hasFastifyRequest ( ctx ) ) {
99
+ ctx . req . log . error ( error ) ;
100
+ } else {
101
+ fallbackHandler ( error ) ;
102
+ }
103
+ }
104
+ } ) ;
105
+ }
106
+
83
107
function useNoIntrospection ( params : {
84
108
signature : string ;
85
109
isNonProductionEnvironment : boolean ;
@@ -95,72 +119,77 @@ function useNoIntrospection(params: {
95
119
} ;
96
120
}
97
121
122
+ export function useHiveSentry ( ) {
123
+ return useSentry ( {
124
+ startTransaction : false ,
125
+ renameTransaction : false ,
126
+ /**
127
+ * When it's not `null`, the plugin modifies the error object.
128
+ * We end up with an unintended error masking, because the GraphQLYogaError is replaced with GraphQLError (without error.originalError).
129
+ */
130
+ eventIdKey : null ,
131
+ operationName : ( ) => 'graphql' ,
132
+ includeRawResult : false ,
133
+ includeResolverArgs : false ,
134
+ includeExecuteVariables : true ,
135
+ configureScope ( args , scope ) {
136
+ // Get the operation name from the request, or use the operation name from the document.
137
+ const operationName =
138
+ args . operationName ??
139
+ args . document . definitions . find ( isOperationDefinitionNode ) ?. name ?. value ??
140
+ 'unknown' ;
141
+
142
+ scope . setContext ( 'Extra Info' , {
143
+ operationName,
144
+ variables : JSON . stringify ( args . variableValues ) ,
145
+ operation : print ( args . document ) ,
146
+ userId : extractUserId ( args . contextValue as any ) ,
147
+ } ) ;
148
+ } ,
149
+ appendTags : ( { contextValue } ) => {
150
+ const supertokens_user_id = extractUserId ( contextValue as any ) ;
151
+ const request_id = ( contextValue as Context ) . requestId ;
152
+
153
+ return {
154
+ supertokens_user_id,
155
+ request_id,
156
+ } ;
157
+ } ,
158
+ skip ( args ) {
159
+ // It's the readiness check
160
+ return args . operationName === 'readiness' ;
161
+ } ,
162
+ } ) ;
163
+ }
164
+
165
+ export function useHiveTracing ( tracingProvider ?: Parameters < typeof useOpenTelemetry > [ 1 ] ) {
166
+ return useOpenTelemetry (
167
+ {
168
+ document : true ,
169
+ resolvers : false ,
170
+ result : false ,
171
+ variables : variables => {
172
+ if ( variables && typeof variables === 'object' && 'selector' in variables ) {
173
+ return JSON . stringify ( variables . selector ) ;
174
+ }
175
+
176
+ return '' ;
177
+ } ,
178
+ excludedOperationNames : [ 'readiness' ] ,
179
+ } ,
180
+ tracingProvider ,
181
+ ) ;
182
+ }
183
+
98
184
export const graphqlHandler = ( options : GraphQLHandlerOptions ) : RouteHandlerMethod => {
99
185
const server = createYoga < Context > ( {
100
186
logging : options . logger ,
101
187
plugins : [
102
188
useArmor ( ) ,
103
- useSentry ( {
104
- startTransaction : false ,
105
- renameTransaction : false ,
106
- /**
107
- * When it's not `null`, the plugin modifies the error object.
108
- * We end up with an unintended error masking, because the GraphQLYogaError is replaced with GraphQLError (without error.originalError).
109
- */
110
- eventIdKey : null ,
111
- operationName : ( ) => 'graphql' ,
112
- includeRawResult : false ,
113
- includeResolverArgs : false ,
114
- includeExecuteVariables : true ,
115
- configureScope ( args , scope ) {
116
- // Get the operation name from the request, or use the operation name from the document.
117
- const operationName =
118
- args . operationName ??
119
- args . document . definitions . find ( isOperationDefinitionNode ) ?. name ?. value ??
120
- 'unknown' ;
121
-
122
- scope . setContext ( 'Extra Info' , {
123
- operationName,
124
- variables : JSON . stringify ( args . variableValues ) ,
125
- operation : print ( args . document ) ,
126
- userId : extractUserId ( args . contextValue as any ) ,
127
- } ) ;
128
- } ,
129
- appendTags : ( { contextValue } ) => {
130
- const supertokens_user_id = extractUserId ( contextValue as any ) ;
131
- const request_id = ( contextValue as Context ) . requestId ;
132
-
133
- return {
134
- supertokens_user_id,
135
- request_id,
136
- } ;
137
- } ,
138
- skip ( args ) {
139
- // It's the readiness check
140
- return args . operationName === 'readiness' ;
141
- } ,
142
- } ) ,
189
+ useHiveSentry ( ) ,
143
190
useSentryUser ( ) ,
144
- useErrorHandler ( ( { errors, context } ) : void => {
145
- // Not sure what changed, but the `context` is now an object with a contextValue property.
146
- // We previously relied on the `context` being the `contextValue` itself.
147
- const ctx = ( 'contextValue' in context ? context . contextValue : context ) as Context ;
148
-
149
- for ( const error of errors ) {
150
- if ( isGraphQLError ( error ) && error . originalError ) {
151
- console . error ( error ) ;
152
- console . error ( error . originalError ) ;
153
- continue ;
154
- } else {
155
- console . error ( error ) ;
156
- }
157
-
158
- if ( hasFastifyRequest ( ctx ) ) {
159
- ctx . req . log . error ( error ) ;
160
- } else {
161
- server . logger . error ( error ) ;
162
- }
163
- }
191
+ useHiveErrorHandler ( error => {
192
+ server . logger . error ( error ) ;
164
193
} ) ,
165
194
useExtendContext ( async context => ( {
166
195
session : await options . authN . authenticate ( context ) ,
@@ -237,24 +266,7 @@ export const graphqlHandler = (options: GraphQLHandlerOptions): RouteHandlerMeth
237
266
} ,
238
267
} ,
239
268
) ,
240
- options . tracing
241
- ? useOpenTelemetry (
242
- {
243
- document : true ,
244
- resolvers : false ,
245
- result : false ,
246
- variables : variables => {
247
- if ( variables && typeof variables === 'object' && 'selector' in variables ) {
248
- return JSON . stringify ( variables . selector ) ;
249
- }
250
-
251
- return '' ;
252
- } ,
253
- excludedOperationNames : [ 'readiness' ] ,
254
- } ,
255
- options . tracing . traceProvider ( ) ,
256
- )
257
- : { } ,
269
+ options . tracing ? useHiveTracing ( options . tracing . traceProvider ( ) ) : { } ,
258
270
useExecutionCancellation ( ) ,
259
271
] ,
260
272
graphiql : ! options . isProduction ,
0 commit comments