32
32
33
33
import type { MaybeRef } from '@vueuse/core' ;
34
34
import { toValue } from '@vueuse/core' ;
35
- import type { ComputedRef } from 'vue' ;
36
- import { computed , getCurrentInstance } from 'vue' ;
35
+ import { computed , type ComputedRef } from 'vue' ;
37
36
38
37
import {
39
38
useQuery , type UseQueryOptions , type UseQueryReturnType ,
@@ -53,9 +52,16 @@ export const useScopedQuery = <TQueryFnData, TError = unknown, TData = TQueryFnD
53
52
options : UseQueryOptions < TQueryFnData , TError , TData > ,
54
53
requiredScopes : [ GrantScope , ...GrantScope [ ] ] ,
55
54
) : UseQueryReturnType < TData , TError > => {
56
- // Warns if `requiredScopes` array is missing or empty during development
57
- if ( import . meta. env . DEV ) {
58
- _warnMissingRequiredScopes ( requiredScopes ) ;
55
+ // [Dev Warning] This query is missing `requiredScopes`.
56
+ // All scoped queries must explicitly define at least one valid scope for clarity and safety.
57
+ if ( import . meta. env . DEV && ( ! requiredScopes || requiredScopes . length === 0 ) ) {
58
+ _warnOncePerTick ( ( ) => {
59
+ console . warn ( '[useScopedQuery] `requiredScopes` is missing or empty.' , {
60
+ queryKey : _extractQueryKey ( ( options as any ) . queryKey ) ,
61
+ suggestion : 'Pass at least one valid scope like [\'DOMAIN\'], [\'WORKSPACE\'], etc.' ,
62
+ } ) ;
63
+ return true ;
64
+ } ) ;
59
65
}
60
66
61
67
const appContextStore = useAppContextStore ( ) ;
@@ -75,15 +81,20 @@ export const useScopedQuery = <TQueryFnData, TError = unknown, TData = TQueryFnD
75
81
return inheritedEnabled && isValidScope . value && isAppReady . value ;
76
82
} ) ;
77
83
78
- // Logs a warning once per queryKey when the current scope is invalid for this query
84
+ // [Dev Warning] The current user's scope is not included in the allowed `requiredScopes`.
85
+ // This usually indicates a configuration mistake in the query declaration.
79
86
if ( import . meta. env . DEV ) {
80
- _warnInvalidScopeOnce ( {
81
- queryKey : _extractQueryKey ( ( options as any ) . queryKey ) ,
82
- enabled : toValue ( queryEnabled ) ,
83
- currentScope : currentGrantScope . value ,
84
- requiredScopes,
85
- isAppReady : isAppReady . value ,
86
- } ) ;
87
+ const currentScope = currentGrantScope . value ;
88
+ if ( isAppReady . value && currentScope && ! requiredScopes . includes ( currentScope ) ) {
89
+ _warnOncePerTick ( ( ) => {
90
+ console . warn ( '[useScopedQuery] Invalid requiredScopes for current scope:' , {
91
+ queryKey : _extractQueryKey ( ( options as any ) . queryKey ) ,
92
+ requiredScopes,
93
+ currentScope,
94
+ } ) ;
95
+ return true ;
96
+ } ) ;
97
+ }
87
98
}
88
99
89
100
return useQuery < TQueryFnData , TError , TData > ( {
@@ -95,72 +106,30 @@ export const useScopedQuery = <TQueryFnData, TError = unknown, TData = TQueryFnD
95
106
const _extractQueryKey = ( input : unknown ) : QueryKeyArray => toValue ( input as ComputedRef < QueryKeyArray > ) ;
96
107
97
108
98
- // Warns if `requiredScopes` array is missing or empty during development
99
- const _warnMissingRequiredScopes = ( scopes : GrantScope [ ] ) => {
100
- if ( import . meta. env . DEV && ( ! scopes || scopes . length === 0 ) ) {
101
- console . warn ( '[useScopedQuery] `requiredScopes` is missing or empty.' , {
102
- suggestion : 'Pass at least one valid scope like [\'DOMAIN\'], [\'WORKSPACE\'], etc.' ,
103
- } ) ;
104
- }
105
- } ;
106
109
107
- /**
108
- * Logs a warning when a query is not executed due to invalid scope,
109
- * but only once per queryKey during development.
110
- *
111
- * Conditions to trigger warning:
112
- * - `enabled` is true
113
- * - app is ready (not loading)
114
- * - currentScope is defined
115
- * - currentScope NOT included in requiredScopes
116
- */
117
- const _warnedKeysPerInstance = new WeakMap < object , Set < string > > ( ) ;
118
- const _warnInvalidScopeOnce = ( params : {
119
- queryKey : QueryKeyArray ;
120
- enabled : boolean ;
121
- currentScope : GrantScope | undefined ;
122
- requiredScopes : GrantScope [ ] ;
123
- isAppReady : boolean ;
124
- } ) => {
125
- if ( ! import . meta. env . DEV ) return ;
126
-
127
- // Get the current Vue component instance (used to scope the warning cache)
128
- const instance = getCurrentInstance ( ) ;
129
- if ( ! instance ) return ;
130
-
131
- const {
132
- queryKey, enabled, currentScope, requiredScopes, isAppReady,
133
- } = params ;
134
-
135
- if ( ! isAppReady || ! currentScope ) return ;
136
-
137
- const isValidScope = requiredScopes . includes ( currentScope ) ;
138
-
139
- if ( ! enabled && isValidScope ) return ;
140
-
141
- if ( isValidScope ) return ;
142
-
143
- // Safely serialize the queryKey (even if it contains objects)
144
- const key = ( ( ) => {
145
- try {
146
- return JSON . stringify ( queryKey ) ;
147
- } catch {
148
- return Array . isArray ( queryKey ) ? queryKey . join ( ':' ) : String ( queryKey ) ;
149
- }
150
- } ) ( ) ;
110
+ /* Warning Logger Utilities */
111
+ const _warnedKeys = new Set < string > ( ) ;
112
+ const _getCallerKey = ( ) : string => {
113
+ try {
114
+ const err = new Error ( ) ;
115
+ const stack = err . stack ?. split ( '\n' ) || [ ] ;
151
116
152
- // Cache per component instance to prevent duplicate logs
153
- let keySet = _warnedKeysPerInstance . get ( instance ) ;
154
- if ( ! keySet ) {
155
- keySet = new Set ( ) ;
156
- _warnedKeysPerInstance . set ( instance , keySet ) ;
157
- }
158
- if ( keySet . has ( key ) ) return ;
159
- keySet . add ( key ) ;
117
+ const caller = stack . find ( ( line , i ) => i > 1
118
+ && ( line . includes ( '.ts' ) || line . includes ( '.vue' ) )
119
+ && ! line . includes ( 'use-scoped-query' ) ) ;
160
120
161
- console . warn ( '[useScopedQuery] Query not executed due to invalid grant scope.' , {
162
- queryKey,
163
- currentScope,
164
- requiredScopes,
165
- } ) ;
121
+ return caller ?. trim ( ) ?? 'UNKNOWN_CALLSITE' ;
122
+ } catch {
123
+ return 'UNKNOWN_CALLSITE' ;
124
+ }
125
+ } ;
126
+ const _warnOncePerTick = ( log : ( ) => boolean ) => {
127
+ const key = _getCallerKey ( ) ;
128
+ if ( _warnedKeys . has ( key ) ) return ;
129
+ const didLog = log ( ) ;
130
+
131
+ if ( didLog ) {
132
+ _warnedKeys . add ( key ) ;
133
+ queueMicrotask ( ( ) => _warnedKeys . delete ( key ) ) ;
134
+ }
166
135
} ;
0 commit comments