@@ -11,6 +11,7 @@ import { defineIntegration } from '../integration';
11
11
import { SEMANTIC_ATTRIBUTE_SENTRY_OP , SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes' ;
12
12
import { captureException } from '../exports' ;
13
13
import { SPAN_STATUS_ERROR , SPAN_STATUS_OK } from '../tracing' ;
14
+ import { DEBUG_BUILD } from '../debug-build' ;
14
15
15
16
const AUTH_OPERATIONS_TO_INSTRUMENT = [
16
17
'reauthenticate' ,
@@ -64,25 +65,25 @@ export const FILTER_MAPPINGS = {
64
65
not : 'not' ,
65
66
} ;
66
67
67
- export const AVAILABLE_OPERATIONS = [ 'select' , 'insert' , 'upsert' , 'update' , 'delete' ] ;
68
+ export const DB_OPERATIONS_TO_INSTRUMENT = [ 'select' , 'insert' , 'upsert' , 'update' , 'delete' ] ;
68
69
69
70
type AuthOperationFn = ( ...args : unknown [ ] ) => Promise < unknown > ;
70
71
type AuthOperationName = ( typeof AUTH_OPERATIONS_TO_INSTRUMENT ) [ number ] ;
71
72
type AuthAdminOperationName = ( typeof AUTH_ADMIN_OPERATIONS_TO_INSTRUMENT ) [ number ] ;
72
- type PostgrestQueryOperationName = ( typeof AVAILABLE_OPERATIONS ) [ number ] ;
73
- type PostgrestQueryOperationFn = ( ...args : unknown [ ] ) => PostgrestFilterBuilder ;
73
+ type PostgRESTQueryOperationName = ( typeof DB_OPERATIONS_TO_INSTRUMENT ) [ number ] ;
74
+ type PostgRESTQueryOperationFn = ( ...args : unknown [ ] ) => PostgRESTFilterBuilder ;
74
75
75
76
export interface SupabaseClientInstance {
76
77
auth : {
77
78
admin : Record < AuthAdminOperationName , AuthOperationFn > ;
78
79
} & Record < AuthOperationName , AuthOperationFn > ;
79
80
}
80
81
81
- export interface PostgrestQueryBuilder {
82
- [ key : PostgrestQueryOperationName ] : PostgrestQueryOperationFn ;
82
+ export interface PostgRESTQueryBuilder {
83
+ [ key : PostgRESTQueryOperationName ] : PostgRESTQueryOperationFn ;
83
84
}
84
85
85
- export interface PostgrestFilterBuilder {
86
+ export interface PostgRESTFilterBuilder {
86
87
method : string ;
87
88
headers : Record < string , string > ;
88
89
url : URL ;
@@ -116,18 +117,36 @@ export interface SupabaseBreadcrumb {
116
117
117
118
export interface SupabaseClientConstructor {
118
119
prototype : {
119
- from : ( table : string ) => PostgrestQueryBuilder ;
120
+ from : ( table : string ) => PostgRESTQueryBuilder ;
120
121
} ;
121
122
}
122
123
123
- export interface PostgrestProtoThenable {
124
+ export interface PostgRESTProtoThenable {
124
125
then : < T > (
125
126
onfulfilled ?: ( ( value : T ) => T | PromiseLike < T > ) | null ,
126
127
onrejected ?: ( ( reason : any ) => T | PromiseLike < T > ) | null ,
127
128
) => Promise < T > ;
128
129
}
129
130
130
- const instrumented = new Map ( ) ;
131
+ type SentryInstrumented < T > = T & {
132
+ __SENTRY_INSTRUMENTED__ ?: boolean ;
133
+ } ;
134
+
135
+ function markAsInstrumented < T > ( fn : T ) : void {
136
+ try {
137
+ ( fn as SentryInstrumented < T > ) . __SENTRY_INSTRUMENTED__ = true ;
138
+ } catch {
139
+ // ignore errors here
140
+ }
141
+ }
142
+
143
+ function isInstrumented < T > ( fn : T ) : boolean | undefined {
144
+ try {
145
+ return ( fn as SentryInstrumented < T > ) . __SENTRY_INSTRUMENTED__ ;
146
+ } catch {
147
+ return false ;
148
+ }
149
+ }
131
150
132
151
/**
133
152
* Extracts the database operation type from the HTTP method and headers
@@ -198,14 +217,8 @@ export function translateFiltersIntoMethods(key: string, query: string): string
198
217
}
199
218
200
219
function instrumentAuthOperation ( operation : AuthOperationFn , isAdmin = false ) : AuthOperationFn {
201
- if ( instrumented . has ( operation ) ) {
202
- return operation ;
203
- }
204
-
205
220
return new Proxy ( operation , {
206
221
apply ( target , thisArg , argumentsList ) {
207
- instrumented . set ( operation , true ) ;
208
-
209
222
const span = startInactiveSpan ( {
210
223
name : operation . name ,
211
224
attributes : {
@@ -255,23 +268,34 @@ function instrumentSupabaseAuthClient(supabaseClientInstance: SupabaseClientInst
255
268
return ;
256
269
}
257
270
258
- AUTH_OPERATIONS_TO_INSTRUMENT . forEach ( ( operation : AuthOperationName ) => {
271
+
272
+ for ( const operation of AUTH_OPERATIONS_TO_INSTRUMENT ) {
259
273
const authOperation = auth [ operation ] ;
260
- if ( typeof authOperation === 'function' ) {
274
+
275
+ if ( ! authOperation ) {
276
+ continue ;
277
+ }
278
+
279
+ if ( typeof supabaseClientInstance . auth [ operation ] === 'function' ) {
261
280
supabaseClientInstance . auth [ operation ] = instrumentAuthOperation ( authOperation ) ;
262
281
}
263
- } ) ;
282
+ }
264
283
265
- AUTH_ADMIN_OPERATIONS_TO_INSTRUMENT . forEach ( ( operation : AuthAdminOperationName ) => {
266
- const authAdminOperation = auth . admin [ operation ] ;
267
- if ( typeof authAdminOperation === 'function' ) {
268
- supabaseClientInstance . auth . admin [ operation ] = instrumentAuthOperation ( authAdminOperation , true ) ;
284
+ for ( const operation of AUTH_ADMIN_OPERATIONS_TO_INSTRUMENT ) {
285
+ const authOperation = auth . admin [ operation ] ;
286
+
287
+ if ( ! authOperation ) {
288
+ continue ;
269
289
}
270
- } ) ;
290
+
291
+ if ( typeof supabaseClientInstance . auth . admin [ operation ] === 'function' ) {
292
+ supabaseClientInstance . auth . admin [ operation ] = instrumentAuthOperation ( authOperation , true ) ;
293
+ }
294
+ }
271
295
}
272
296
273
297
function instrumentSupabaseClientConstructor ( SupabaseClient : unknown ) : void {
274
- if ( instrumented . has ( SupabaseClient ) ) {
298
+ if ( isInstrumented ( ( SupabaseClient as unknown as SupabaseClientConstructor ) . prototype . from ) ) {
275
299
return ;
276
300
}
277
301
@@ -280,33 +304,29 @@ function instrumentSupabaseClientConstructor(SupabaseClient: unknown): void {
280
304
{
281
305
apply ( target , thisArg , argumentsList ) {
282
306
const rv = Reflect . apply ( target , thisArg , argumentsList ) ;
283
- const PostgrestQueryBuilder = ( rv as PostgrestQueryBuilder ) . constructor ;
307
+ const PostgRESTQueryBuilder = ( rv as PostgRESTQueryBuilder ) . constructor ;
284
308
285
- instrumentPostgrestQueryBuilder ( PostgrestQueryBuilder as unknown as new ( ) => PostgrestQueryBuilder ) ;
309
+ instrumentPostgRESTQueryBuilder ( PostgRESTQueryBuilder as unknown as new ( ) => PostgRESTQueryBuilder ) ;
286
310
287
311
return rv ;
288
312
} ,
289
313
} ,
290
314
) ;
315
+
316
+ markAsInstrumented ( ( SupabaseClient as unknown as SupabaseClientConstructor ) . prototype . from ) ;
291
317
}
292
318
293
- // This is the only "instrumented" part of the SDK. The rest of instrumentation
294
- // methods are only used as a mean to get to the `PostgrestFilterBuilder` constructor itself.
295
- function instrumentPostgrestFilterBuilder ( PostgrestFilterBuilder : PostgrestFilterBuilder [ 'constructor' ] ) : void {
296
- if ( instrumented . has ( PostgrestFilterBuilder ) ) {
319
+ function instrumentPostgRESTFilterBuilder ( PostgRESTFilterBuilder : PostgRESTFilterBuilder [ 'constructor' ] ) : void {
320
+ if ( isInstrumented ( ( PostgRESTFilterBuilder . prototype as unknown as PostgRESTProtoThenable ) . then ) ) {
297
321
return ;
298
322
}
299
323
300
- instrumented . set ( PostgrestFilterBuilder , {
301
- then : ( PostgrestFilterBuilder . prototype as unknown as PostgrestProtoThenable ) . then ,
302
- } ) ;
303
-
304
- ( PostgrestFilterBuilder . prototype as unknown as PostgrestProtoThenable ) . then = new Proxy (
305
- ( PostgrestFilterBuilder . prototype as unknown as PostgrestProtoThenable ) . then ,
324
+ ( PostgRESTFilterBuilder . prototype as unknown as PostgRESTProtoThenable ) . then = new Proxy (
325
+ ( PostgRESTFilterBuilder . prototype as unknown as PostgRESTProtoThenable ) . then ,
306
326
{
307
327
apply ( target , thisArg , argumentsList ) {
308
- const operations = AVAILABLE_OPERATIONS ;
309
- const typedThis = thisArg as PostgrestFilterBuilder ;
328
+ const operations = DB_OPERATIONS_TO_INSTRUMENT ;
329
+ const typedThis = thisArg as PostgRESTFilterBuilder ;
310
330
const operation = extractOperation ( typedThis . method , typedThis . headers ) ;
311
331
312
332
if ( ! operations . includes ( operation ) ) {
@@ -427,44 +447,43 @@ function instrumentPostgrestFilterBuilder(PostgrestFilterBuilder: PostgrestFilte
427
447
} ,
428
448
} ,
429
449
) ;
430
- }
431
450
432
- function instrumentPostgrestQueryBuilder ( PostgrestQueryBuilder : new ( ) => PostgrestQueryBuilder ) : void {
433
- if ( instrumented . has ( PostgrestQueryBuilder ) ) {
434
- return ;
435
- }
451
+ markAsInstrumented ( ( PostgRESTFilterBuilder . prototype as unknown as PostgRESTProtoThenable ) . then ) ;
452
+ }
436
453
437
- // We need to wrap _all_ operations despite them sharing the same `PostgrestFilterBuilder`
454
+ function instrumentPostgRESTQueryBuilder ( PostgRESTQueryBuilder : new ( ) => PostgRESTQueryBuilder ) : void {
455
+ // We need to wrap _all_ operations despite them sharing the same `PostgRESTFilterBuilder`
438
456
// constructor, as we don't know which method will be called first, and we don't want to miss any calls.
439
- for ( const operation of AVAILABLE_OPERATIONS ) {
440
- instrumented . set ( PostgrestQueryBuilder , {
441
- [ operation ] : ( PostgrestQueryBuilder . prototype as Record < string , unknown > ) [
442
- operation as 'select' | 'insert' | 'upsert' | 'update' | 'delete'
443
- ] as ( ...args : unknown [ ] ) => PostgrestFilterBuilder ,
444
- } ) ;
445
-
446
- type PostgrestOperation = keyof Pick < PostgrestQueryBuilder , 'select' | 'insert' | 'upsert' | 'update' | 'delete' > ;
447
- ( PostgrestQueryBuilder . prototype as Record < string , any > ) [ operation as PostgrestOperation ] = new Proxy (
448
- ( PostgrestQueryBuilder . prototype as Record < string , any > ) [ operation as PostgrestOperation ] ,
457
+ for ( const operation of DB_OPERATIONS_TO_INSTRUMENT ) {
458
+ if ( isInstrumented ( ( PostgRESTQueryBuilder . prototype as Record < string , any > ) [ operation ] ) ) {
459
+ continue ;
460
+ }
461
+
462
+ type PostgRESTOperation = keyof Pick < PostgRESTQueryBuilder , 'select' | 'insert' | 'upsert' | 'update' | 'delete' > ;
463
+ ( PostgRESTQueryBuilder . prototype as Record < string , any > ) [ operation as PostgRESTOperation ] = new Proxy (
464
+ ( PostgRESTQueryBuilder . prototype as Record < string , any > ) [ operation as PostgRESTOperation ] ,
449
465
{
450
466
apply ( target , thisArg , argumentsList ) {
451
467
const rv = Reflect . apply ( target , thisArg , argumentsList ) ;
452
- const PostgrestFilterBuilder = ( rv as PostgrestFilterBuilder ) . constructor ;
468
+ const PostgRESTFilterBuilder = ( rv as PostgRESTFilterBuilder ) . constructor ;
453
469
454
- logger . log ( `Instrumenting ${ operation } operation's PostgrestFilterBuilder ` ) ;
470
+ DEBUG_BUILD && logger . log ( `Instrumenting ${ operation } operation's PostgRESTFilterBuilder ` ) ;
455
471
456
- instrumentPostgrestFilterBuilder ( PostgrestFilterBuilder ) ;
472
+ instrumentPostgRESTFilterBuilder ( PostgRESTFilterBuilder ) ;
457
473
458
474
return rv ;
459
475
} ,
460
476
} ,
461
477
) ;
478
+
479
+ markAsInstrumented ( ( PostgRESTQueryBuilder . prototype as Record < string , any > ) [ operation ] ) ;
462
480
}
463
481
}
464
482
465
483
const instrumentSupabase = ( supabaseClientInstance : unknown ) : void => {
466
484
if ( ! supabaseClientInstance ) {
467
- throw new Error ( 'Supabase client instance is not available.' ) ;
485
+ DEBUG_BUILD && logger . warn ( 'Supabase integration was not installed because no Supabase client was provided.' ) ;
486
+ return ;
468
487
}
469
488
const SupabaseClientConstructor =
470
489
supabaseClientInstance . constructor === Function ? supabaseClientInstance : supabaseClientInstance . constructor ;
@@ -477,7 +496,7 @@ const INTEGRATION_NAME = 'Supabase';
477
496
478
497
const _supabaseIntegration = ( supabaseClient => {
479
498
// Instrumenting here instead of `setup` or `setupOnce` because we may need to instrument multiple clients.
480
- // So we don't want the instrumentation is skipped because the integration is already installed.
499
+ // So we don't want the instrumentation skipped because the integration is already installed.
481
500
instrumentSupabase ( supabaseClient ) ;
482
501
483
502
return {
0 commit comments