@@ -11,6 +11,7 @@ import { defineIntegration } from '../integration';
1111import { SEMANTIC_ATTRIBUTE_SENTRY_OP , SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '../semanticAttributes' ;
1212import { captureException } from '../exports' ;
1313import { SPAN_STATUS_ERROR , SPAN_STATUS_OK } from '../tracing' ;
14+ import { DEBUG_BUILD } from '../debug-build' ;
1415
1516const AUTH_OPERATIONS_TO_INSTRUMENT = [
1617 'reauthenticate' ,
@@ -64,25 +65,25 @@ export const FILTER_MAPPINGS = {
6465 not : 'not' ,
6566} ;
6667
67- export const AVAILABLE_OPERATIONS = [ 'select' , 'insert' , 'upsert' , 'update' , 'delete' ] ;
68+ export const DB_OPERATIONS_TO_INSTRUMENT = [ 'select' , 'insert' , 'upsert' , 'update' , 'delete' ] ;
6869
6970type AuthOperationFn = ( ...args : unknown [ ] ) => Promise < unknown > ;
7071type AuthOperationName = ( typeof AUTH_OPERATIONS_TO_INSTRUMENT ) [ number ] ;
7172type 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 ;
7475
7576export interface SupabaseClientInstance {
7677 auth : {
7778 admin : Record < AuthAdminOperationName , AuthOperationFn > ;
7879 } & Record < AuthOperationName , AuthOperationFn > ;
7980}
8081
81- export interface PostgrestQueryBuilder {
82- [ key : PostgrestQueryOperationName ] : PostgrestQueryOperationFn ;
82+ export interface PostgRESTQueryBuilder {
83+ [ key : PostgRESTQueryOperationName ] : PostgRESTQueryOperationFn ;
8384}
8485
85- export interface PostgrestFilterBuilder {
86+ export interface PostgRESTFilterBuilder {
8687 method : string ;
8788 headers : Record < string , string > ;
8889 url : URL ;
@@ -116,18 +117,36 @@ export interface SupabaseBreadcrumb {
116117
117118export interface SupabaseClientConstructor {
118119 prototype : {
119- from : ( table : string ) => PostgrestQueryBuilder ;
120+ from : ( table : string ) => PostgRESTQueryBuilder ;
120121 } ;
121122}
122123
123- export interface PostgrestProtoThenable {
124+ export interface PostgRESTProtoThenable {
124125 then : < T > (
125126 onfulfilled ?: ( ( value : T ) => T | PromiseLike < T > ) | null ,
126127 onrejected ?: ( ( reason : any ) => T | PromiseLike < T > ) | null ,
127128 ) => Promise < T > ;
128129}
129130
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+ }
131150
132151/**
133152 * Extracts the database operation type from the HTTP method and headers
@@ -198,14 +217,8 @@ export function translateFiltersIntoMethods(key: string, query: string): string
198217}
199218
200219function instrumentAuthOperation ( operation : AuthOperationFn , isAdmin = false ) : AuthOperationFn {
201- if ( instrumented . has ( operation ) ) {
202- return operation ;
203- }
204-
205220 return new Proxy ( operation , {
206221 apply ( target , thisArg , argumentsList ) {
207- instrumented . set ( operation , true ) ;
208-
209222 const span = startInactiveSpan ( {
210223 name : operation . name ,
211224 attributes : {
@@ -255,23 +268,34 @@ function instrumentSupabaseAuthClient(supabaseClientInstance: SupabaseClientInst
255268 return ;
256269 }
257270
258- AUTH_OPERATIONS_TO_INSTRUMENT . forEach ( ( operation : AuthOperationName ) => {
271+
272+ for ( const operation of AUTH_OPERATIONS_TO_INSTRUMENT ) {
259273 const authOperation = auth [ operation ] ;
260- if ( typeof authOperation === 'function' ) {
274+
275+ if ( ! authOperation ) {
276+ continue ;
277+ }
278+
279+ if ( typeof supabaseClientInstance . auth [ operation ] === 'function' ) {
261280 supabaseClientInstance . auth [ operation ] = instrumentAuthOperation ( authOperation ) ;
262281 }
263- } ) ;
282+ }
264283
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 ;
269289 }
270- } ) ;
290+
291+ if ( typeof supabaseClientInstance . auth . admin [ operation ] === 'function' ) {
292+ supabaseClientInstance . auth . admin [ operation ] = instrumentAuthOperation ( authOperation , true ) ;
293+ }
294+ }
271295}
272296
273297function instrumentSupabaseClientConstructor ( SupabaseClient : unknown ) : void {
274- if ( instrumented . has ( SupabaseClient ) ) {
298+ if ( isInstrumented ( ( SupabaseClient as unknown as SupabaseClientConstructor ) . prototype . from ) ) {
275299 return ;
276300 }
277301
@@ -280,33 +304,29 @@ function instrumentSupabaseClientConstructor(SupabaseClient: unknown): void {
280304 {
281305 apply ( target , thisArg , argumentsList ) {
282306 const rv = Reflect . apply ( target , thisArg , argumentsList ) ;
283- const PostgrestQueryBuilder = ( rv as PostgrestQueryBuilder ) . constructor ;
307+ const PostgRESTQueryBuilder = ( rv as PostgRESTQueryBuilder ) . constructor ;
284308
285- instrumentPostgrestQueryBuilder ( PostgrestQueryBuilder as unknown as new ( ) => PostgrestQueryBuilder ) ;
309+ instrumentPostgRESTQueryBuilder ( PostgRESTQueryBuilder as unknown as new ( ) => PostgRESTQueryBuilder ) ;
286310
287311 return rv ;
288312 } ,
289313 } ,
290314 ) ;
315+
316+ markAsInstrumented ( ( SupabaseClient as unknown as SupabaseClientConstructor ) . prototype . from ) ;
291317}
292318
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 ) ) {
297321 return ;
298322 }
299323
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 ,
306326 {
307327 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 ;
310330 const operation = extractOperation ( typedThis . method , typedThis . headers ) ;
311331
312332 if ( ! operations . includes ( operation ) ) {
@@ -427,44 +447,43 @@ function instrumentPostgrestFilterBuilder(PostgrestFilterBuilder: PostgrestFilte
427447 } ,
428448 } ,
429449 ) ;
430- }
431450
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+ }
436453
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`
438456 // 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 ] ,
449465 {
450466 apply ( target , thisArg , argumentsList ) {
451467 const rv = Reflect . apply ( target , thisArg , argumentsList ) ;
452- const PostgrestFilterBuilder = ( rv as PostgrestFilterBuilder ) . constructor ;
468+ const PostgRESTFilterBuilder = ( rv as PostgRESTFilterBuilder ) . constructor ;
453469
454- logger . log ( `Instrumenting ${ operation } operation's PostgrestFilterBuilder ` ) ;
470+ DEBUG_BUILD && logger . log ( `Instrumenting ${ operation } operation's PostgRESTFilterBuilder ` ) ;
455471
456- instrumentPostgrestFilterBuilder ( PostgrestFilterBuilder ) ;
472+ instrumentPostgRESTFilterBuilder ( PostgRESTFilterBuilder ) ;
457473
458474 return rv ;
459475 } ,
460476 } ,
461477 ) ;
478+
479+ markAsInstrumented ( ( PostgRESTQueryBuilder . prototype as Record < string , any > ) [ operation ] ) ;
462480 }
463481}
464482
465483const instrumentSupabase = ( supabaseClientInstance : unknown ) : void => {
466484 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 ;
468487 }
469488 const SupabaseClientConstructor =
470489 supabaseClientInstance . constructor === Function ? supabaseClientInstance : supabaseClientInstance . constructor ;
@@ -477,7 +496,7 @@ const INTEGRATION_NAME = 'Supabase';
477496
478497const _supabaseIntegration = ( supabaseClient => {
479498 // 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.
481500 instrumentSupabase ( supabaseClient ) ;
482501
483502 return {
0 commit comments