Skip to content

Commit 3de0c5c

Browse files
committed
fix(use-scoped-query): improve dev-logger with err.stack and queueMicrotask
Signed-off-by: piggggggggy <[email protected]>
1 parent 0520c37 commit 3de0c5c

File tree

1 file changed

+47
-78
lines changed

1 file changed

+47
-78
lines changed

Diff for: apps/web/src/query/composables/use-scoped-query.ts

+47-78
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@
3232

3333
import type { MaybeRef } from '@vueuse/core';
3434
import { toValue } from '@vueuse/core';
35-
import type { ComputedRef } from 'vue';
36-
import { computed, getCurrentInstance } from 'vue';
35+
import { computed, type ComputedRef } from 'vue';
3736

3837
import {
3938
useQuery, type UseQueryOptions, type UseQueryReturnType,
@@ -53,9 +52,16 @@ export const useScopedQuery = <TQueryFnData, TError = unknown, TData = TQueryFnD
5352
options: UseQueryOptions<TQueryFnData, TError, TData>,
5453
requiredScopes: [GrantScope, ...GrantScope[]],
5554
): 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+
});
5965
}
6066

6167
const appContextStore = useAppContextStore();
@@ -75,15 +81,20 @@ export const useScopedQuery = <TQueryFnData, TError = unknown, TData = TQueryFnD
7581
return inheritedEnabled && isValidScope.value && isAppReady.value;
7682
});
7783

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.
7986
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+
}
8798
}
8899

89100
return useQuery<TQueryFnData, TError, TData>({
@@ -95,72 +106,30 @@ export const useScopedQuery = <TQueryFnData, TError = unknown, TData = TQueryFnD
95106
const _extractQueryKey = (input: unknown): QueryKeyArray => toValue(input as ComputedRef<QueryKeyArray>);
96107

97108

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-
};
106109

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') || [];
151116

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'));
160120

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+
}
166135
};

0 commit comments

Comments
 (0)